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);
}
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;
}
}
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);
}
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();
}
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);
}
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()
O 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
}
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')
}
}
}
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)
}
}
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"),
}
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.
}
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)