Rust

Como Usar Structs em Rust: Definição, Instanciação e Métodos em Produção

8 min de leitura

Como Usar Structs em Rust: Definição, Instanciação e Métodos em Produção

Definição e Conceitos Fundamentais Structs (estruturas) são um dos pilares da programação orientada a dados em Rust. Elas permitem agrupar múltiplos valores relacionados em uma única entidade nomeada, cada um com seu próprio tipo. Diferentemente de tuplas, os campos de uma struct possuem nomes descritivos, tornando o código mais legível e mantível. Rust oferece três tipos de structs: com campos nomeados, com campos sem nome (tuple struct) e sem campos (unit struct). A declaração básica de uma struct é simples. Você define a estrutura com a palavra-chave , seguida do nome e seus campos. Cada campo deve ter um tipo explícito anotado. Uma struct é um tipo produto — combina múltiplos tipos em um único tipo composto. Ao contrário de enums (tipos soma), uma struct contém sempre todos os seus campos, nunca apenas alguns. Instanciação de Structs Para usar uma struct, você precisa criar uma instância fornecendo valores para todos os campos. Rust exige que todos os campos sejam inicializados

<h2>Definição e Conceitos Fundamentais</h2>

<p>Structs (estruturas) são um dos pilares da programação orientada a dados em Rust. Elas permitem agrupar múltiplos valores relacionados em uma única entidade nomeada, cada um com seu próprio tipo. Diferentemente de tuplas, os campos de uma struct possuem nomes descritivos, tornando o código mais legível e mantível. Rust oferece três tipos de structs: com campos nomeados, com campos sem nome (tuple struct) e sem campos (unit struct).</p>

<p>A declaração básica de uma struct é simples. Você define a estrutura com a palavra-chave <code>struct</code>, seguida do nome e seus campos. Cada campo deve ter um tipo explícito anotado. Uma struct é um tipo produto — combina múltiplos tipos em um único tipo composto. Ao contrário de enums (tipos soma), uma struct contém sempre todos os seus campos, nunca apenas alguns.</p>

<pre><code class="language-rust">// Struct com campos nomeados

struct Pessoa {

nome: String,

idade: u32,

email: String,

}

// Tuple struct (campos sem nome)

struct Ponto(f64, f64);

// Unit struct (sem campos)

struct Marcador;</code></pre>

<h2>Instanciação de Structs</h2>

<p>Para usar uma struct, você precisa criar uma instância fornecendo valores para todos os campos. Rust exige que todos os campos sejam inicializados explicitamente — não há valores padrão implícitos. A sintaxe é intuitiva: nome da struct seguido de chaves contendo pares campo-valor.</p>

<p>A instanciação segue o princípio de propriedade do Rust. Valores movidos para a struct se tornam propriedade dela. Se você passar uma <code>String</code> para um campo, ela não poderá mais ser usada na variável original sem clonagem. Para structs que implementam <code>Copy</code> (como números inteiros), isso não é problema pois eles são copiados automaticamente.</p>

<pre><code class="language-rust">fn main() {

// Instanciação simples

let pessoa1 = Pessoa {

nome: String::from(&quot;Alice&quot;),

idade: 28,

email: String::from(&quot;alice@example.com&quot;),

};

// Sintaxe de campo abreviado

let nome = String::from(&quot;Bob&quot;);

let idade = 35;

let email = String::from(&quot;bob@example.com&quot;);

let pessoa2 = Pessoa {

nome, // equivalente a nome: nome

idade, // equivalente a idade: idade

email,

};

// Usando função construtora

let pessoa3 = criar_pessoa(

&quot;Carlos&quot;,

30,

&quot;carlos@example.com&quot;,

);

// Tuple struct

let ponto = Ponto(3.14, 2.71);

println!(&quot;X: {}, Y: {}&quot;, ponto.0, ponto.1);

// Acessando campos

println!(&quot;{} tem {} anos&quot;, pessoa1.nome, pessoa1.idade);

}

fn criar_pessoa(nome: &amp;str, idade: u32, email: &amp;str) -&gt; Pessoa {

Pessoa {

nome: String::from(nome),

idade,

email: String::from(email),

}

}</code></pre>

<p>Uma prática recomendada é criar funções associadas que atuam como construtores. Isso encapsula a lógica de inicialização e torna o código mais seguro e flexível para futuras mudanças.</p>

<h2>Métodos e Funções Associadas</h2>

<p>Métodos são funções vinculadas a uma struct através do bloco <code>impl</code>. Eles diferem de funções simples porque recebem <code>self</code> (ou <code>&amp;self</code>, <code>&amp;mut self</code>) como primeiro parâmetro, representando uma instância da struct. Funções associadas não recebem <code>self</code> e são geralmente usadas como construtores.</p>

<p>O sistema de ownership do Rust se integra perfeitamente com métodos. Um método pode receber <code>&amp;self</code> (empréstimo imutável), <code>&amp;mut self</code> (empréstimo mutável) ou <code>self</code> (posse total). Escolher o tipo correto é crucial para permitir composição e reutilização de código sem erros de borrow.</p>

<pre><code class="language-rust">impl Pessoa {

// Função associada (construtor padrão)

fn nova(nome: &amp;str, idade: u32, email: &amp;str) -&gt; Self {

Pessoa {

nome: String::from(nome),

idade,

email: String::from(email),

}

}

// Método que pega empréstimo imutável

fn apresentar(&amp;self) {

println!(

&quot;Olá, meu nome é {} e tenho {} anos&quot;,

self.nome, self.idade

);

}

// Método que retorna informações

fn eh_maior_de_idade(&amp;self) -&gt; bool {

self.idade &gt;= 18

}

// Método que pega empréstimo mutável

fn fazer_aniversario(&amp;mut self) {

self.idade += 1;

println!(&quot;{} agora tem {} anos&quot;, self.nome, self.idade);

}

// Método que consome self

fn converter_em_string(self) -&gt; String {

format!(

&quot;{} ({} anos) - {}&quot;,

self.nome, self.idade, self.email

)

}

}

impl Ponto {

fn distancia_origem(&amp;self) -&gt; f64 {

(self.0.powi(2) + self.1.powi(2)).sqrt()

}

fn deslocar(&amp;mut self, dx: f64, dy: f64) {

self.0 += dx;

self.1 += dy;

}

}

fn main() {

let mut pessoa = Pessoa::nova(&quot;Diana&quot;, 25, &quot;diana@example.com&quot;);

pessoa.apresentar();

println!(&quot;Maior de idade? {}&quot;, pessoa.eh_maior_de_idade());

pessoa.fazer_aniversario();

// Isso consome pessoa — não pode mais ser usada depois

let resumo = pessoa.converter_em_string();

println!(&quot;{}&quot;, resumo);

let mut ponto = Ponto(3.0, 4.0);

println!(&quot;Distância: {}&quot;, ponto.distancia_origem());

ponto.deslocar(1.0, 1.0);

println!(&quot;Novo ponto: ({}, {})&quot;, ponto.0, ponto.1);

}</code></pre>

<h2>Boas Práticas e Padrões Comuns</h2>

<p>Estruturar bem suas structs é fundamental para código limpo e eficiente. Use structs para agrupar dados relacionados, não como simples containers de valores sem contexto. Implemente construtores através de funções associadas (<code>new</code> é a convenção) para facilitar a criação de instâncias. Considere derivar traits úteis como <code>Debug</code>, <code>Clone</code> e <code>Copy</code> quando apropriado.</p>

<p>Um padrão poderoso é usar <code>impl</code> separadamente para diferentes grupos de métodos. Você pode ter múltiplos blocos <code>impl</code> para a mesma struct, facilitando organização lógica. Lembre-se: Rust força você a pensar sobre ownership de forma explícita, o que previne bugs sutis em outras linguagens. Abraçe isso ao desenhar seus tipos.</p>

<pre><code class="language-rust">#[derive(Debug, Clone)]

struct Retangulo {

largura: f64,

altura: f64,

}

impl Retangulo {

fn novo(largura: f64, altura: f64) -&gt; Self {

Retangulo { largura, altura }

}

}

// Separar grupos de funcionalidade

impl Retangulo {

fn area(&amp;self) -&gt; f64 {

self.largura * self.altura

}

fn perimetro(&amp;self) -&gt; f64 {

2.0 * (self.largura + self.altura)

}

}

impl Retangulo {

fn pode_conter(&amp;self, outro: &amp;Retangulo) -&gt; bool {

self.largura &gt;= outro.largura &amp;&amp; self.altura &gt;= outro.altura

}

}

fn main() {

let ret1 = Retangulo::novo(30.0, 50.0);

let ret2 = ret1.clone();

println!(&quot;Área: {}&quot;, ret1.area());

println!(&quot;Perímetro: {}&quot;, ret1.perimetro());

println!(&quot;Contém ret2? {}&quot;, ret1.pode_conter(&amp;ret2));

}</code></pre>

<h2>Conclusão</h2>

<p>Nesta aula, dominamos os três elementos essenciais de structs em Rust: definição clara de tipos compostos, instanciação com ownership seguro e métodos integrados ao sistema de borrow. Structs são a base para escrever abstrações robustas e performáticas. O sistema de tipos de Rust torna impossível criar bugs sutis com dados — tudo é verificado em tempo de compilação.</p>

<p>Pratique criando structs para seus próprios domínios: usuários, produtos, eventos. Experimente com diferentes combinações de <code>self</code>, <code>&amp;self</code> e <code>&amp;mut self</code> até internalizar quando cada um faz sentido. Com structs bem desenhadas, você escreverá código que é simultaneamente seguro, eficiente e expressivo.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://doc.rust-lang.org/book/ch05-00-structs.html" target="_blank" rel="noopener noreferrer">The Rust Book - Structs</a></li>

<li><a href="https://doc.rust-lang.org/rust-by-example/custom_types/structs.html" target="_blank" rel="noopener noreferrer">Rust by Example - Structs</a></li>

<li><a href="https://doc.rust-lang.org/std/" target="_blank" rel="noopener noreferrer">Official Rust Documentation - std</a></li>

<li><a href="https://rust-lang.github.io/api-guidelines/" target="_blank" rel="noopener noreferrer">Rust API Guidelines</a></li>

</ul>

Comentários

Mais em Rust

Dominando Variáveis, Mutabilidade e Tipos Primitivos em Rust em Projetos Reais
Dominando Variáveis, Mutabilidade e Tipos Primitivos em Rust em Projetos Reais

Variáveis e Imutabilidade em Rust Em Rust, toda variável é imutável por padrã...

Construindo APIs REST com Axum em Rust: Do Básico ao Avançado
Construindo APIs REST com Axum em Rust: Do Básico ao Avançado

Introdução ao Axum e sua Arquitetura Axum é um framework web moderno construí...

O que Todo Dev Deve Saber sobre Biblioteca anyhow: Tratamento de Erros em Aplicações Rust
O que Todo Dev Deve Saber sobre Biblioteca anyhow: Tratamento de Erros em Aplicações Rust

Por Que Tratamento de Erros em Rust é Diferente Em Rust, erros não são exceçõ...