Structs são tipos de dados personalizados que permitem nomear e agrupar vários valores relacionados.
Para definir uma struct, use a palavra-chave struct seguida de um nome e de chaves contendo os campos com seus 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, informe o nome da struct e use chaves com pares chave-valor para os campos.
let user = User {
active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1
};
Use a notação de ponto para acessar ou modificar os campos de uma instância mutável de struct.
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 valor em si permaneça 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 escopo é o intervalo dentro de um programa em que 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 de escopo, ela é destruída, o que garante que o recurso associado seja liberado corretamente.
Isso é feito implementando ou derivando o trait Drop, que sinaliza ao compilador que o tipo pode ser destruído com segurança quando não for mais útil.
#[derive(Drop)]
struct A {}
fn main() {
A {}; // this value can be destroyed after going out of scope
}
No momento, a implementação de Drop pode ser derivada para todos os tipos, exceto dicionários (Felt252Dict) e tipos que contenham dicionários.
Um valor também pode ser movido, o que acontece quando um valor é passado para outra função, destruindo a variável original que se referia ao valor (tornando essa variável inútil) e criando uma nova variável para guardar 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
}
Retornar valores equivale a movê-los.
O exemplo abaixo mostra uma função que retorna 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 nos proíbe de reutilizá-la depois.
#[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.
}
Nesse caso, tentamos passar o mesmo valor (a struct Point na variável p) para as duas chamadas de função.
Depois que passamos a variável para a função foo pela primeira vez, a variável não pode mais ser usada.
Uma forma de resolver isso é derivar o trait Copy na nossa struct, o que mudaria o que acontece quando passamos uma variável Copy para uma função: ela não moveria mais o valor, mas criaria uma nova variável que se refere ao mesmo valor.
Essa é uma operação sem custo, porque variáveis são uma abstração do Cairo e os valores são sempre imutáveis.
Para implementar o trait Copy no seu tipo, adicione 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 dois recursos para passar um valor sem destruí-lo ou movê-lo, chamados references e snapshots.
Snapshots fornecem uma "visão" imutável de um valor em um determinado momento, permitindo que você mantenha a propriedade de uma variável enquanto acessa seu estado. Qualquer modificação na variável cria uma nova célula de memória, deixando a visão 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 em uma variável comum, você pode usar o operador desnap *, que é o oposto do operador @.
Só é possível aplicar desnap em tipos que implementam o trait Copy.
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);
}
Referências permitem que você passe valores mutáveis para funções e que eles sejam retornados implicitamente, mantendo a propriedade no contexto de chamada.
Isso pode ser feito usando o modificador ref, desde que a variável seja declarada como mutável com a palavra-chave mut.
Podemos atualizar o exemplo anterior de Point para usar referências mutáveis em vez de depender do trait Copy, e ele continuaria funcionando.
#[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 perto de você tem uma associação de estudantes muito ativa. A associação de estudantes é administrada por um presidente e, a cada 2 anos, são realizadas eleições para eleger um novo presidente. Na eleição deste ano, decidiu-se que era necessário um novo sistema digital para contar os votos.
A escola precisa da sua ajuda para construir esse novo sistema.
Uma das primeiras coisas que o novo sistema de votação precisa é de um contador de votos. Esse contador é uma forma de acompanhar os votos que um determinado candidato tem.
Crie uma função new_vote_counter que aceita o número de votos iniciais de um candidato e retorna um snapshot de uma struct VoteCounter, inicializada com o número de votos iniciais fornecido.
let initial_votes: u32 = 2;
let counter: @VoteCounter = new_vote_counter(initial_votes);
// @VoteCounter {
// value: 2,
// }
Agora você tem uma forma de criar novos contadores! Mas agora você percebe que o novo sistema também vai precisar de uma forma de obter o número de votos de um contador.
Crie uma função vote_count que recebe um contador (@VoteCounter) como argumento e retorna o número de votos no contador.
let initial_votes: u32 = 3;
let vote_counter: @VoteCounter = new_vote_counter(initial_votes);
let count = vote_count(vote_counter);
// => 3
Finalmente chegou a hora de contar os votos! Agora você precisa de uma forma de incrementar os votos de um contador.
Crie uma função increment_vote_count que recebe um contador (@VoteCounter) como argumento e um número de votos, e incrementa o contador nesse 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, chegou a hora de preparar o anúncio do resultado para toda a escola. Para isso, você percebe que ter apenas contadores de votos é insuficiente. É preciso haver uma forma de associar o número de votos a um determinado candidato.
Crie uma função new_election_result que recebe o nome de um candidato e o número de votos dele e retorna um novo resultado de eleição.
let result: ElectionResult = new_election_result("Peter", 3);
// ElectionResult {
// name: "Peter",
// votes: @VoteCounter {
// value: 3,
// },
// }
A struct de resultado da eleição já foi criada para você e está definida assim:
struct ElectionResult {
name: ByteArray,
counter: @VoteCounter,
}
Chegou a hora de anunciar o novo presidente para a escola! O novo presidente será anunciado nos pequenos painéis digitais de mensagens que a escola tem.
A mensagem deve mostrar o nome do novo presidente e os votos que ele recebeu, no seguinte formato: <candidate_name> (<votes>).
Este é um exemplo de mensagem assim: "Peter (51)".
Crie uma função display_result que recebe um @ElectionResult como argumento e retorna um ByteArray com a mensagem a ser exibida.
let result = ElectionResult {
name: "John",
votes: @VoteCounter {
value: 32,
},
};
let msg = display_result(@result);
// => "John (32)"
Para garantir que os resultados finais estivessem corretos, os votos foram recontados. Na recontagem, descobriu-se que o número de votos de alguns candidatos estava errado por um.
Crie uma função decrement_votes_of_candidate que recebe os resultados finais e o nome de um candidato cuja contagem de votos você deve decrementar.
Os resultados finais são fornecidos na 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
Crie sua conta no Exercism para aprender e dominar Cairo com 25 conceitos68 exercícios e mentoria humana de verdade, tudo de graça.