As structs são tipos de dados personalizados que te permitem dar um nome e agrupar vários valores relacionados.
Para definir uma struct, usa a palavra-chave struct seguida de um nome e de chavetas que contêm os campos com os respetivos nomes e tipos:
#[derive(Drop)]
struct User {
active: bool,
username: ByteArray,
email: ByteArray,
sign_in_count: u64,
}
Para criar uma instância de uma struct, indica o nome da struct e usa chavetas com pares chave-valor para os campos.
let user = User {
active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1
};
Usa a notação de ponto para aceder aos campos de uma instância de struct mutável ou para os modificar.
let mut user = User {
active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1
};
user.email = "anotheremail@example.com";
Os valores em Cairo são imutáveis, e essa imutabilidade garante que, mesmo que muitas variáveis se refiram ao mesmo valor, o próprio valor permanece constante e inalterado. O proprietário de uma variável é o código que pode ler (e escrever, se for mutável) essa variável.
Regras de propriedade em Cairo:
Um âmbito é a região de um programa na qual uma variável é válida.
fn main() {
// `x` is not valid here, it has not been declared yet
let x: u32 = 3; // `x` is declared, it is valid from this point forward
// do stuff with `x`
} // this scope ended here, `x` goes out of scope and is no longer valid
Quando uma variável sai do âmbito, é destruída, o que garante que o recurso associado é libertado corretamente.
Isto consegue-se implementando ou derivando o trait Drop, que indica ao compilador que o tipo pode ser destruído em segurança quando já não for útil.
#[derive(Drop)]
struct A {}
fn main() {
A {}; // this value can be destroyed after going out of scope
}
De momento, a implementação de Drop pode ser derivada para todos os tipos, exceto para dicionários (Felt252Dict) e para tipos que contenham dicionários.
Um valor também pode ser movido. Isto acontece quando um valor é passado a outra função: a variável original que se referia ao valor é destruída (ficando assim inútil) e é criada uma nova variável para conter o mesmo valor.
fn foo(num: u32) {
// do something with p
}
fn main() {
let num: u32 = 10;
foo(num);
// ownership of `num` was moved into `foo` and is thus no longer valid in this scope
}
Devolver valores é equivalente a movê-los.
O exemplo abaixo mostra uma função que devolve um valor:
#[derive(Drop)]
struct A {}
fn main() {
// main now owns a1
let a1 = gives_ownership();
// a2 comes into scope
let a2 = A {};
// a2 is moved into takes_and_gives_back, which also moves its return value into a3
let a3 = takes_and_gives_back(a2);
} // Here, a3 and a1 go out of scope and are dropped. a2 was moved, so nothing happens.
// Moves its return value into the function that calls it
fn gives_ownership() -> A {
let some_a = A {};
some_a
}
// This function takes an instance some_a of A and returns it
fn takes_and_gives_back(some_a: A) -> A {
some_a
}
Como uma variável é destruída depois de ser movida, o compilador impede-nos de a reutilizar a seguir.
#[derive(Drop)]
struct Point {
x: u128,
y: u128,
}
fn foo(p: Point) {
// do something with p
}
fn main() {
let p = Point { x: 5, y: 10 };
foo(p);
foo(p); // compiler error: Variable was previously moved.
}
Neste caso, tentamos passar o mesmo valor (a struct Point na variável p) às duas chamadas de função.
Depois de passarmos a variável à função foo pela primeira vez, a variável já não pode ser usada.
Uma forma de resolver isto é derivar o trait Copy na nossa struct, o que alteraria o que acontece quando passamos uma variável Copy a uma função: deixaria de mover o valor e criaria uma nova variável que se refere ao mesmo valor.
Esta é uma operação gratuita, porque as variáveis são uma abstração de Cairo e os valores são sempre imutáveis.
Para implementar o trait Copy no teu tipo, adiciona a anotação #[derive(Copy)] à definição do tipo.
#[derive(Copy, Drop)]
struct Point {
x: u128,
y: u128,
}
fn foo(p: Point) {
// do something with p
}
fn main() {
let p = Point { x: 5, y: 10 };
foo(p);
foo(p); // this now works
}
Cairo tem duas funcionalidades para passar um valor sem o destruir ou mover, chamadas references e snapshots.
Os snapshots fornecem uma "vista" imutável de um valor num determinado momento, permitindo-te manter a propriedade de uma variável enquanto acedes ao seu estado. Qualquer modificação à variável cria uma nova célula de memória, deixando a vista do snapshot inalterada.
Para criar um snapshot, basta colocar @ antes da instância da variável.
// this function takes in a snapshot using `@` with the data-type
fn get_len(arr: @Array<u128>) -> u32 { //
arr.len()
}
fn foo(arr: Array<u128>) {
arr.pop_front();
}
fn main() {
let mut new_arr: Array<u128> = array![1, 2, 3, 4, 5, 6, 7, 8];
let snapshot = @new_arr; // using `@` to create a snapshot of a variable
let len = get_len(snapshot); // pass the snapshot as argument, retaining ownership
foo(new_arr); // `new_arr` is still valid up until here and no further
}
Para converter um snapshot de volta numa variável normal, podes usar o operador desnap *, que funciona como o oposto do operador @.
Só os tipos que implementam o trait Copy podem sofrer desnap.
fn square(n: @u32) -> u32 {
let n = *n; // we desnap the value
n * n
}
fn main() {
let num: u32 = 10;
let squared = square(@num); // we use `@` to create a snapshot
println!("{} squared = {}", num, squared);
}
As referências permitem-te passar valores mutáveis a funções e recebê-los de volta implicitamente, mantendo a propriedade no contexto de quem chama.
Isto consegue-se com o modificador ref, desde que a variável seja declarada como mutável com a palavra-chave mut.
Podemos atualizar o exemplo anterior da struct Point para usar referências mutáveis em vez de depender do trait Copy, e continuaria a funcionar.
#[derive(Drop)]
struct Point {
x: u128,
y: u128,
}
fn foo(ref p: Point) {
// we can now update the passed values and the changes
// will be visible in the calling context
p.x += 1;
}
fn main() {
let mut p = Point { x: 5, y: 10 };
foo(ref p);
foo(ref p); // this now works even without the Copy trait
println!("p.x = {}", p.x); // p.x = 7
}
Uma escola local perto de ti tem uma associação de estudantes muito ativa. A associação de estudantes é gerida por um presidente e, a cada 2 anos, realizam-se eleições para eleger um novo presidente. Nas eleições deste ano, decidiu-se que era necessário um novo sistema digital para contar os votos.
A escola precisa da tua ajuda para construir este novo sistema.
Uma das primeiras coisas de que o novo sistema de votação precisa é de um contador de votos. Este contador é uma forma de acompanhar os votos que um determinado candidato tem.
Cria uma função new_vote_counter que aceita o número de votos iniciais de um candidato e devolve um snapshot de uma struct VoteCounter, inicializada com o número de votos iniciais indicado.
let initial_votes: u32 = 2;
let counter: @VoteCounter = new_vote_counter(initial_votes);
// @VoteCounter {
// value: 2,
// }
Agora já tens uma forma de criar novos contadores! Mas agora reparas que o novo sistema também vai precisar de uma forma de obter o número de votos de um contador.
Cria uma função vote_count que recebe um contador (@VoteCounter) como argumento e devolve o número de votos do contador.
let initial_votes: u32 = 3;
let vote_counter: @VoteCounter = new_vote_counter(initial_votes);
let count = vote_count(vote_counter);
// => 3
Chegou finalmente a hora de contar os votos! Agora precisas de uma forma de incrementar os votos de um contador.
Cria uma função increment_vote_count que recebe um contador (@VoteCounter) como argumento e um número de votos, e incrementa o contador com esse número de votos.
let votes: u32 = 3;
let vote_counter: @VoteCounter = new_vote_counter(initial_votes);
let vote_counter: VoteCounter = increment_vote_count(vote_counter, 2);
let count = vote_count(@vote_counter);
// => 5
Com todos os votos contados, é hora de preparar o anúncio dos resultados para toda a escola. Para isso, reparas que ter apenas contadores para os votos é insuficiente. É preciso haver uma forma de associar o número de votos a um determinado candidato.
Cria uma função new_election_result que recebe o nome de um candidato e o seu número de votos e devolve um novo resultado eleitoral.
let result: ElectionResult = new_election_result("Peter", 3);
// ElectionResult {
// name: "Peter",
// votes: @VoteCounter {
// value: 3,
// },
// }
A struct do resultado eleitoral já está criada para ti e está definida como:
struct ElectionResult {
name: ByteArray,
counter: @VoteCounter,
}
É hora de anunciar o novo presidente à escola! O presidente será anunciado nos pequenos painéis de mensagens digitais que a escola tem.
A mensagem deve mostrar o nome do novo presidente e os votos que teve, no seguinte formato: <candidate_name> (<votes>).
Este é um exemplo dessa mensagem: "Peter (51)".
Cria uma função display_result que recebe um @ElectionResult como argumento e devolve um ByteArray com a mensagem a apresentar.
let result = ElectionResult {
name: "John",
votes: @VoteCounter {
value: 32,
},
};
let msg = display_result(@result);
// => "John (32)"
Para garantir que os resultados finais estavam corretos, os votos foram recontados. Na recontagem, verificou-se que o número de votos de alguns candidatos estava errado por uma unidade.
Cria uma função decrement_votes_of_candidate que recebe os resultados finais e o nome de um candidato cuja contagem de votos deves decrementar.
Os resultados finais são dados sob a forma de um Felt252Dict<u32>, em que as chaves são os nomes dos candidatos e os valores são o total de votos de cada um.
let mut final_results: Felt252Dict<u32> = Default::default();
final_results.insert('Mary', 10);
final_results.insert('John', 51);
decrement_votes_of_candidate(ref final_results, 'Mary');
let decremented_votes = final_results.get('Mary');
// => 9
Inscreve-te no Exercism para aprenderes e dominares Cairo com 25 conceitos68 exercícios, e mentoria humana real, tudo grátis.