Trilhas
/
Cairo
Cairo
/
Exercícios
/
Dia da eleição
Dia da eleição

Dia da eleição

Exercício de aprendizagem

Introdução

Structs

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";

Propriedade

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:

  1. Cada variável em Cairo tem um proprietário.
  2. Só pode haver um proprietário por vez.
  3. Quando o proprietário sai de escopo, a variável é destruída.

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
}

Referências e snapshots

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 
}

Instruções

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.

1. Crie um contador de votos

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,
// }

2. Obtenha o número de votos de um contador

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

3. Incremente os votos de um contador

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

4. Crie os resultados da eleição

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,
}

5. Anuncie os resultados

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)"

6. Recontagem de votos

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
Editar via GitHub O link abre em uma nova janela ou aba
Cairo Exercism

Tudo pronto para começar Dia da eleição?

Crie sua conta no Exercism para aprender e dominar Cairo com 25 conceitos68 exercícios e mentoria humana de verdade, tudo de graça.