O que são structs?

Compreender a programação orientada a objetos é uma obrigação para qualquer desenvolvedor. A programação orientada a objetos envolve a criação de classes, que atuam como descrições ou projetos de um objeto. O objeto é normalmente composto de várias variáveis ​​ou funções.

Em linguagens como C, Go e Rust, as classes não são um recurso. Em vez disso, essas linguagens usam structs, que definem apenas um grupo de propriedades. Embora os structs não permitam que você defina métodos, Rust e Go definem funções de uma forma que forneça acesso aos structs.

Neste tutorial, aprenderemos o básico de como os structs operam no Rust. Vamos começar!

O que é Rust?

Rust , a linguagem de programação criada pela Mozilla, preenche um papel semelhante ao C por ser uma linguagem rápida e de baixo nível que usa padrões de sintaxe modernos e um gerenciador de pacotes central.

Escrevendo uma estrutura em Rust

No código a seguir, escreveremos uma estrutura simples para um tipo Cat que inclui o nome e a idade das propriedades. Depois de definir nossa estrutura, definiremos nossa função principal.

Criaremos uma nova string e uma nova instância da estrutura, passando as propriedades de nome e idade. Vamos imprimir toda a estrutura e interpolar suas propriedades em uma string. Para o nome, usaremos Scratchy. Para idade, usaremos 4:

//Este atributo de depuração implementa fmt:: Debug que nos permitirá//imprimir a estrutura usando {:?} # [Derivar (Depurar)]//declarando uma estrutura struct Cat {//nome da propriedade digitada como String nome do tipo: String,//idade digitada como inteiro sem sinal de 8 bits age: u8} fn main () {//cria objeto string com o nome do gato let catname=String:: from (“Scratchy”);//Cria uma instância de struct e salva em uma variável let scratchy=Cat {name: catname, age: 4};

Observe que estamos usando o atributo derive, que abordaremos em detalhes posteriormente, para automatizar a implementação de certas características em nossa estrutura. Como derivamos a característica de depuração, podemos imprimir a estrutura inteira usando {:?}:

//usando {:?} Para imprimir a estrutura inteira println! (“{:?}”, arranhado);//usando propriedades individuais em uma String println! (“{} tem {} anos!”, scratchy.name, scratchy.age); }

Existem vários itens importantes a serem observados nesta seção. Primeiro, como com qualquer valor em Rust, cada propriedade na estrutura deve ser tipos. Além disso, certifique-se de considerar a diferença entre uma string (um objeto de string ou estrutura) e & str (um ponteiro para uma string). Como estamos usando o tipo de string, temos que criar uma string a partir de um literal de string adequado.

O atributo derivar

Por padrão, as estruturas não são imprimíveis. Uma estrutura deve implementar a função stc:: fmt:: debug para usar o formatador {:?} Com println !. No entanto, em nosso exemplo de código acima, usamos o atributo derive (Debug) em vez de implementar uma característica manualmente. Este atributo nos permite imprimir structs para facilitar a depuração.

Os atributos agem como diretivas para o compilador escrever o clichê. Existem vários outros atributos derivados no Rust que podemos usar para permitir que o compilador implemente certas características para nós:

[#derive (hash)]: converte a estrutura em uma hash [#derive (clone)]: adiciona um método clone para duplicar a estrutura [#derive (eq)]: implementa a eq traço, definindo igualdade como todas as propriedades com o mesmo valor

características de estrutura

Podemos criar uma estrutura com propriedades, mas como podemos vinculá-los a funções como fazemos com classes em outras linguagens?

Rust usa um recurso chamado traits , que define um pacote de funções para estruturas implementar. Um benefício dos traços é que você pode usá-los para digitar. Você pode criar funções que podem ser usadas por quaisquer estruturas que implementem o mesmo traço. Essencialmente, você pode construir métodos em estruturas, desde que implemente a característica certa.

Usar características para fornecer métodos permite uma prática chamada composição, que também é usada em Go. Em vez de ter classes que normalmente herdam métodos de uma classe pai, qualquer estrutura pode misturar e combinar as características de que precisa sem usar uma hierarquia.

Escrevendo uma característica

Vamos continuar nosso exemplo de cima, definindo uma estrutura Cat e Dog. Gostaríamos que ambos tivessem uma função de aniversário e som. Definiremos a assinatura dessas funções em um traço chamado Pet.

No exemplo abaixo, usaremos Spot como o nome para Dog. Usamos vai Ruff como som e 0 para idade. Para o gato, usaremos go Meow como o som e 1 para a idade. A função para aniversário é self.age +=1 ;:

//Criar structs # [derive (Debug)] struct Cat {name: String, age: u8} # [derive (Debug)] struct Dog {name: String, age: u8}//Declara o traço de struct Pet {//Esta nova função atua como um construtor//permitindo-nos adicionar lógica adicional para instanciar um struct//Este método particular pertence ao traço fn new (nome: String)-> Self;//Assinatura de outras funções que pertencem a esta característica//incluímos uma versão mutável da estrutura em birthday fn birthday (& mut self); som fn (& self); }//Implementamos o trait for cat//definimos os métodos cujas assinaturas estavam no trait impl Pet for Cat {fn new (name: String)-> Cat {return Cat {name, age: 0}; } fn aniversário (& mut self) {self.age +=1; println! (“Feliz Aniversário {}, agora você é {}”, self.name, self.age); } fn sound (& self) {println! (“{} vai miau!”, self.name); }}//Implementamos a característica para cachorro//apenas definimos som. Aniversário e nome já definidos impl Pet for Dog {fn new (name: String)-> Dog {return Dog {name, age: 0}; } fn aniversário (& mut self) {self.age +=1; println! (“Feliz Aniversário {}, agora você é {}”, self.name, self.age); } fn sound (& self) {println! (“{} vai ruff!”, self.name); }}

Observe que definimos um novo método que atua como um construtor. Em vez de criar um novo Cat como fizemos em nosso trecho anterior, podemos apenas digitar nossa nova variável!

Quando invocarmos o construtor, ele usará a nova implementação daquele tipo específico de estrutura. Portanto, tanto o Cão quanto o Gato poderão usar as funções Aniversário e Som:

fn main () {//Criar estruturas usando a nova função Pet//usando a variável de tipo para determinar qual//implementação usar deixe mut scratchy: Cat=Pet:: new (String:: from (“Scratchy”)); deixe mut spot: Dog=Pet:: new (String:: from (“Spot”));//usando o método de aniversário scratchy.birthday (); spot.birthday ();//usando o método de som scratchy.sound (); spot.sound (); }

Existem várias coisas importantes a serem observadas sobre as características. Por um lado, você deve definir a função para cada estrutura que implementa a característica. Você pode fazer isso criando definições padrão na definição do trait.

Declaramos os structs usando a palavra-chave mut porque os structs podem ser modificados por funções. Por exemplo, o aniversário incrementa a idade e altera as propriedades da estrutura, portanto, passamos o parâmetro como uma referência mutável à estrutura (& mut self).

Neste exemplo, usamos um método estático para inicializar uma nova estrutura, o que significa que o tipo das novas variáveis ​​é determinado pelo tipo de struct.

Retornando um struct

Às vezes, uma função pode retornar vários structs possíveis, o que ocorre quando vários structs implementam o mesmo traço. Para escrever este tipo de função, basta digitar o valor de retorno de uma estrutura que implementa a característica desejada.

Vamos continuar com nosso exemplo anterior e retornar Pet dentro de um objeto Box:

//Nós dinamicamente return Pet dentro de um objeto Box fn new_pet (species: & str, name: String)-> Box {

No exemplo acima, usamos o tipo Box para o valor de retorno, o que nos permite alocar memória suficiente para qualquer struct implementando o trait Pet. Podemos definir uma função que retorna qualquer tipo de estrutura Pet em nossa função, desde que a envolvamos em uma nova caixa.

Criamos uma função que instancia nosso Pet sem especificar a idade, passando uma string de digite Animal de estimação e nome. Usando instruções if, podemos determinar que tipo de Pet instanciar:

if species==”Cat”{return Box:: new (Cat {name, age: 0}); } else {return Box:: new (Dog {name, age: 0}); }}

A função retorna um tipo Box, que representa a memória sendo alocada para um objeto que implementa Pet. Quando criamos Scratchy e Spot, não precisamos mais digitar as variáveis. Apresentamos a lógica explicitamente na função em que um Cachorro ou Gato seria retornado:

fn main () {//Criar estruturas usando o método new_pet let mut scratchy=new_pet (“Cat”, String:: from (“Scratchy”)); deixe mut spot=new_pet (“Dog”, String:: from (“Spot”));//usando o método de aniversário scratchy.birthday (); spot.birthday ();//usando o método de som scratchy.sound (); spot.sound (); }

Resumo

Nós aprendemos o seguinte sobre structs em Rust:

Structs nos permitem agrupar propriedades em uma única estrutura de dados. Usando traits, podemos implementar métodos diferentes em uma struct Digitando com traits nos permite escrever funções que podem receber e retornar structs O atributo derive nos permite implementar certas características em nossos structs com facilidade

Agora, podemos implementar padrões de projeto orientados a objetos típicos em Rust usando composição sobre herança.