Official link
18.Ownership
Cairo borrows Rust's ownership system to achieve memory safety and high performance.
In Cairo, each data has an "owner", and the owner's scope determines the life cycle of the value. When the owner goes out of scope, Cairo automatically cleans up the value and any resources it uses, a process called "Drop".
Cairo's ownership rules are as follows:
Every value in Cairo has an owner.
A value has exactly one owner at any time.
When the owner (variable) goes out of scope, this value will be discarded.
A value cannot leave scope without its ownership being moved.
19.scope
In Cairo, scope is the part of your code where a variable is valid and available for use. It is defined by a pair of curly braces {}. When a variable enters scope, it is valid until it goes out of scope. When it goes out of scope, its value is discarded and any memory or resources it held are released.
#[starknet::contract]
mod ownership_scope{
#[storage]
struct Storage{
}
fn scope_function() {
let x = 'hello'; // x comes into scope
// x can be used here
let y = x;
} // x goes out of scope and is dropped here
fn scope_nested() {
let outer_var = 'outer'; // outer_var is in the outer scope
{
let inner_var = 'inner'; // inner_var is in the inner scope
}
// inner_var is out of scope here
// outer_var is still in scope here
let x = outer_var;
}
}
20.Move
In Cairo, the programming language used in StarkNet, the keyword "move" is used to handle the movement and transfer of assets. It is used to implement asset transfers, transactions, and allocations within smart contracts on StarkNet.
In Cairo, the "move" keyword is typically used in conjunction with variables and updates to the contract state. It can be used to transfer assets from one address (or contract) to another, or to update the state variables within a contract.
Here's a simple example that demonstrates the usage of the "move" keyword in Cairo within the context of StarkNet:
contract MyContract:
storage:
var balances: Map(address, uint256)
# Asset transfer
function transfer(to: address, amount: uint256):
# Check if the sender's balance is sufficient
assert balances[msg_sender()] >= amount
# Update the balances of the sender and the receiver
balances[msg_sender()] -= amount
balances[to] += amount
# Get balance
function getBalance(address: address) -> uint256:
return balances[address]
In this example, MyContract is a simple contract that maintains a storage variable balances, which is a mapping (Map) used to store addresses and their corresponding balances.
The transfer function uses the "move" keyword to implement asset transfer. It first checks if the sender's balance is sufficient to perform the transfer (using the assert statement). Then, it uses the "move" keyword to transfer the assets from the sender's address to the receiver's address by updating the corresponding balances in the balances mapping.
The getBalance function is used to retrieve the balance of a specified address. It simply returns the value associated with the given address in the balances mapping.
It's important to note that the usage of the "move" keyword in Cairo within StarkNet is subject to specific rules and constraints related to asset ownership and transfer to ensure security and correctness.
This is just a simple example showcasing the usage of the "move" keyword in Cairo within StarkNet. In reality, Cairo in StarkNet offers more advanced features and syntax for implementing complex smart contract logic.
21.PreserveOwnership
#Return ownership via function
use array::ArrayTrait;
fn return_function(){
let mut x = ArrayTrait::<felt252>::new();
x = return_ownership(x);
let y = x;
}
fn return_ownership(some_array: Array<felt252>) -> Array<felt252> {
some_array
}
#Copy
#[derive(Copy, Drop)]
struct Point {
x: u128,
y: u128,
}
fn copy_struct(){
let p1 = Point { x: 5, y: 10 };
let p2 = p1;
let p3 = p1;
}
#Clone
use array::ArrayTrait;
use clone::Clone;
use array::ArrayTCloneImpl;
fn clone_example(){
let x = ArrayTrait::<felt252>::new();
let y = x.clone();
let z = x;
}
#mutable reference
fn reference_example(){
let mut x = ArrayTrait::<felt252>::new();
use_reference(ref x);
let y = x;
}
fn use_reference(ref some_array: Array<felt252>) {
}
#snapshot
fn snapshot_example(){
let x = ArrayTrait::<felt252>::new();
use_snapshot(@x);
let y = x;
}
fn use_snapshot(some_array: @Array<felt252>) {
}
22.generics
In Cairo, generics are a general programming mechanism that allows you to write reusable code to handle different types of data without having to rewrite similar code in each case. This chapter introduces you to generics in Cairo and shows you how to use them to write flexible and reusable code.
Note that using generics may increase the size of the Starknet contract.
In Cairo, generics are a general programming mechanism that allows you to write reusable code to handle different types of data without having to rewrite similar code in each case. Using generics, you can write functions, structures, enumerations, and methods so that they accept different types of parameters or have different types of fields. Such codes are called "generic codes" because they generally apply to multiple concrete types.
Generic functions in Cairo are defined using type parameters. Type parameters are specified in angle brackets < > after the function name
Similar to functions, you can also create generic structures and enumerations. Type parameters are specified in angle brackets < > after the structure or enumeration name.
You can also define generic methods in structures or enumerations. To do this, you need to specify the type parameters after the Implementation Name.
Cairo allows you to impose constraints on types using generics. Constraints ensure that generic code only works with types that meet specific requirements
23.interface
In Cairo, interfaces are traits marked with the #[starknet::interface] attribute, which function similarly to Solidity. The rules are as follows:
Decorators for functions must be declared explicitly.
The functions therein should not be implemented.
Constructors should not be declared.
State variables should not be declared.
Events should not be declared (unlike Solidity).
All view functions need to include the parameter self: @TContractState, and external functions need to include the parameter ref self: TContractState.
Let’s rewrite Solidity’s IERC20 contract using Cairo:
use starknet::ContractAddress;
#[starknet::interface]
trait IERC20<TContractState> {
fn name(self: @TContractState) -> felt252;
fn symbol(self: @TContractState) -> felt252;
fn decimals(self: @TContractState) -> u8;
fn total_supply(self: @TContractState) -> u256;
fn balance_of(self: @TContractState, account: ContractAddress) -> u256;
fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256;
fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool;
fn transfer_from(
ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256
) -> bool;
fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool;
}

