WEB3DEV

Cover image for Explorando alguns conceitos do Cairo 1.0 (Parte 2/2).
Adriano P. Araujo
Adriano P. Araujo

Posted on

Explorando alguns conceitos do Cairo 1.0 (Parte 2/2).

Image description

Foto de Markus Spiske em Unsplash

Bem-vindo de volta! Em nosso artigo anterior, abordamos variáveis e tipos primitivos no Cairo 1.0. Se você perdeu, pode lê-lo aqui (traduzido

Hoje, investiremos em alguns conceitos mais avançados no Cairo 1.0, incluindo Tuplas, Arrays, Enums, Options e Traits. Esses conceitos ajudarão você a escrever um código mais complexo e robusto no Cairo. Então, vamos começar!

Tuplas

As tuplas são uma maneira de agrupar vários valores com vários tipos em um tipo composto. Uma vez declaradas, as tuplas têm um comprimento fixo e não podem aumentar ou diminuir.


fn main() {

    let tuple: (u32, u8, bool) = (10, 20, true);

}

Enter fullscreen mode Exit fullscreen mode

Tuplas são úteis para a desestruturação. Aqui está um exemplo:


fn main() {

    let tup: (u32, u8, bool) = (10, 20, true);



    let (x, y, z) = tup;



    if x == 6 {

        return x;

    } else {

        return y;

    }

}

Enter fullscreen mode Exit fullscreen mode

Este programa primeiro cria uma tupla e a vincula à variável tup. Em seguida, usa um padrão com let para pegar tup e transformá-la em três variáveis separadas, x, ye z.

Arrays

Uma outra maneira de ter uma coleção de vários valores, assim como tuplas, mas todo elemento de um array deve ter os mesmos tipos.

Uma coisa importante a ser observada é os arrays serem apenas anexadas, significando que você só pode adicionar elementos ao final de um array. Isso tem a ver com o fato de que, uma vez gravado um slot de memória, ele não pode ser substituído, mas apenas lido.

O array tem o seguinte método:

Criando um array

**append()**


use array::ArrayTrait;

use option::OptionTrait;



fn create_array() -> Array::<felt> {

    let mut a = ArrayTrait::new(); // something to change here...

    a.append(0);

    a.append(1);

    a.append(2);

}

Enter fullscreen mode Exit fullscreen mode

Removendo um elemento de um array

**pop_front()**


use option::OptionTrait;

use array::ArrayTrait;

use debug::PrintTrait;



fn main() {

    let mut a = ArrayTrait::new();

    a.append(0);

    a.append(1);

    a.append(2);



    let first_value = a.pop_front().unwrap();

    first_value.print();



}

Enter fullscreen mode Exit fullscreen mode

O código acima imprime 0 quando removemos o primeiro elemento.

Acessando elementos do array

get(), at()

Para acessar um elemento do array, você pode usar o método get ( ) ou at( ), que possui funcionalidade diferente.

A função get de um array retorna um Option<Box<@T>>. Isso significa que, se o índice especificado corresponder a um elemento existente no array, o get retorna um ponteiro para uma caixa contendo um instantâneo desse elemento. Por outro lado, se o índice especificado estiver fora dos limites, get retorna None.

O objetivo da função  get é fornecer uma maneira de acessar índices de array que podem não estar nos limites do array, evitando pânico. Retornando um Option, get permite que você lide graciosamente com o caso em que o índice está fora dos limites, sem travar o programa.




fn main() {

    let mut a = ArrayTrait::new();

    a.append(0);

    a.append(1);



    let first = *a.at(0_usize);

    let second = *a.at(1_usize);

}

Enter fullscreen mode Exit fullscreen mode

Neste exemplo, a variável nomeada primeiro receberá o valor 0, porque esse é o valor no índice 0 no array. A variável nomeada em segundo receberá o valor 1 do índice 1 no array.

A função at de um array retorna diretamente um instantâneo do elemento no índice especificado usando o operador  unbox( ) para extrair o valor armazenado em uma caixa. No entanto, se o índice especificado estiver fora dos limites,  at causa um erro de pânico.

Você deve usar  at somente quando você deseja que seu programa entre em pânico se o índice fornecido estiver fora dos limites do array. Esse comportamento pode ajudar a evitar comportamentos inesperados, notificando-o imediatamente sobre um problema no seu código.

Acessando o comprimento de um array

len()

len() é usado para retornar o comprimento de um array.


use array::ArrayTrait;

fn main() -> u128 {

    let mut arr = ArrayTrait::<u128>::new();

    arr.append(100_u128);

    let length = arr.len();

    length;

//retorna 1

}

Enter fullscreen mode Exit fullscreen mode

Correspondência de Enums e Pattern

Os enums permitem definir um tipo enumerando suas possíveis variantes. Vejamos como definir e usar um enum. Em seguida, observaremos a correspondência de padrões usando match facilitando a execução de códigos diferentes para valores diferentes de um enum. Exploraremos um enum útil chamado Option, que expressa se um valor pode ser algo ou nada.


use debug::print;

use debug::print_felt;

enum Message { // FAZ:define alguns tipos de mensagens, conforme usado abaixo

Quit:(),

Echo:(),

Move:(),

ChangeColor:(),

}



fn main() {

    Message::Quit(()).print();

    Message::Echo(()).print();

    Message::Move(()).print();

    Message::ChangeColor(()).print();

}



trait PrintTrait<T> {

    fn print(self: T);

}



impl MessagePrintImpl of PrintTrait::<Message> {

    fn print(self: Message) {

        match self {

            Message::Quit(()) => print_felt('Quit'),

            Message::Echo(()) => print_felt('Echo'),

            Message::Move(()) => print_felt('Move'),

            Message::ChangeColor(()) => print_felt('ChangeColor')

        }

    }

}

Enter fullscreen mode Exit fullscreen mode

O código acima define um enum chamado Message que possui quatro variantes: Quit, Echo, Move, e ChangeColor. A função fn main() chama o método print() em cada variante do enum Message.

O trait PrintTrait<T> é definido com um único método  print() implementado para o enum Message usando o bloco impl. Na implementação, a expressão  match é usada para corresponder ao padrão da variante do  Message enumerando e imprimindo uma mensagem correspondente usando a função print_felt().

Em resumo, o código define um enum com variantes e usa a correspondência de padrões para executar ações diferentes com base na variante do enum. O trait PrintTrait e sua implementação são usados para imprimir a mensagem correspondente para cada variante do enum  Message.

Opções

É um tipo genérico que representa a possibilidade de ter um valor ou não. É semelhante ao tipo Option do rust ou ao tipoMaybe do Haskell.

O tipo Option é definido na biblioteca padrão do Cairo 1.0 e é usado para lidar com casos em que uma função pode ou não retornar um valor. Possui dois valores possíveis:  Some(value)  que representa um valor presente e  None que representa um valor ausente.

O Option  é frequentemente usado em conjunto com a correspondência de padrões para lidar com os dois casos possíveis de ter um valor ou não.

Suponha que tenhamos uma função que tente dividir dois números e retorne um  Option<f64>  representando o resultado da divisão. Se o segundo número for zero, a função retornará None  para indicar que a divisão é indefinida.


fn divide(x: f64, y: f64) -> Option<f64> {

    if y == 0.0 {

        None

    } else {

        Some(x / y)

    }

}

Enter fullscreen mode Exit fullscreen mode

Para usar essa função, podemos corresponder ao resultado da divisão para lidar com os dois casos possíveis: ou a divisão foi bem-sucedida e temos um valor, ou a divisão ficou indefinida e não temos valor.


use debug::print;

use debug::print_felt;

let result1 = divide(10.0, 2.0);

match result1 {

    Some(value) => print_felt("Result: {}", value),

    None => print_felt("Division by zero"),

}



let result2 = divide(10.0, 0.0);

match result2 {

    Some(value) => print_felt("Result: {}", value),

    None => print_felt("Division by zero"),

}

Enter fullscreen mode Exit fullscreen mode

No primeiro exemplo, chamamos divide(10.0, 2.0) que retorna Some(5.0). O padrão match de expressão corresponde a Some(value) e imprime o resultado da divisão como Result: 5.0.

No segundo exemplo, chamamos divide(10.0, 0.0) que retorna None. O padrão match de expressão corresponde a None e imprime uma mensagem dizendo Division by zero

Em resumo, Option no Cairo 1.0 é um tipo que representa a possibilidade de ter um valor ou não, e é frequentemente usado com a correspondência de padrões para lidar com os dois casos possíveis. O valor None é usado para representar a ausência de um valor, normalmente quando uma operação é indefinida ou não pode ser executada.

Traits

Define o comportamento compartilhado entre os tipos. Eles são semelhantes às interfaces em outras linguagens e permitem definir um conjunto de métodos que um tipo deve implementar para ser considerado um membro de uma trait específica, enquanto os impls fornecem a implementação real dessas funções.

Aqui está um exemplo básico de uma trait no Cairo:


trait Display<T> {

    fn display(x: T) -> Array<u8>;

}



impl DisplayUsize of Display<usize> {

    fn display(x: usize) -> Array<u8> {

        ...

    }

}



fn main() {

    // Pode ser chamado a partir do trait.

    let a = Display::display(5_usize);

    //Pode ser chamado a partir do implk.

    let b = DisplayUsize::display(5_usize);

    // Não pode ser chamado pelo tipo

    // T::display(value) - Não funciona.

}



Enter fullscreen mode Exit fullscreen mode

Observe que, diferentemente do Rust, impls tem nomes, para poderem ser explicitamente especificados.

No Cairo, a palavra-chave of é usada nos impls para especificar a trait concreta que está sendo implementada, em vez de implementar a trait para um tipo específico, conforme feito em Rust com a palavra-chave for. A principal diferença entre o Cairo e o Rust nesse contexto é que o Cairo não possui uma relação direta de implementação de tipo para trait. Em vez disso, o Cairo enfatiza a implementação direta da trait, com o nome da trait concreta especificada após a palavra-chave of.

Em resumo, as traits no Cairo permitem definir o comportamento compartilhado entre os tipos, especificando um conjunto de métodos que um tipo deve implementar para ser considerado um membro de uma trait específica.

Em suma, abordamos alguns dos conceitos mais avançados do Cairo 1.0, incluindo tuplas, arrays, enums, options e trait. Esses conceitos são essenciais para escrever códigos mais complexos e robustos no Cairo. Vimos como declarar e usar tuplas e arrays, como definir e usar enums e como usar a correspondência de padrões com match.

Também exploramos o enum da option, útil para lidar com casos em que um valor pode ou não existir. Esses conceitos, juntamente com as variáveis discutidas anteriormente e os tipos primitivos, fornecem uma base sólida para a escrita de programas no Cairo 1.0. Com esses conceitos, você pode criar programas eficientes e confiáveis que podem lidar com uma variedade de tarefas.


Este artigo foi escrito por  Starknet Africa e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Latest comments (0)