Definir o comportamento compartilhado usando características
Uma característica é uma interface comum que um grupo de tipos pode implementar. A biblioteca padrão do Rust tem muitas características úteis, como:
io::Read
para valores que podem ler bytes de uma origem.io::Write
para valores que podem gravar bytes.Debug
para valores que podem ser impressos no console usando o especificador de formato "{:?}".Clone
para valores que podem ser duplicados explicitamente na memória.ToString
para valores que podem ser convertidos em umString
.Default
para tipos que têm um valor padrão sensato, como zero para números, vazio para vetores e "" paraString
.Iterator
para tipos que podem produzir uma sequência de valores.
Cada definição de característica é uma coleção de métodos definidos para um tipo desconhecido, geralmente representando uma funcionalidade ou comportamento que seus implementadores podem apresentar.
Para representar o conceito de "ter uma área bidimensional", podemos definir a seguinte característica:
trait Area {
fn area(&self) -> f64;
}
Aqui, declaramos uma característica usando a palavra-chave trait
e o nome da característica, que é Area
neste caso.
Dentro das chaves, declaramos as assinaturas do método que descrevem os comportamentos dos tipos que implementam essa característica, que neste caso é a assinatura de função fn area(&self) -> f64
. O compilador, então, verificará se cada tipo que implementa essa característica deve fornecer um comportamento personalizado para o corpo do método.
Agora, vamos criar alguns tipos que implementarão nossa característica Area
:
struct Circle {
radius: f64,
}
struct Rectangle {
width: f64,
height: f64,
}
impl Area for Circle {
fn area(&self) -> f64 {
use std::f64::consts::PI;
PI * self.radius.powf(2.0)
}
}
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
Para implementar uma característica para um tipo, usamos as palavras-chave impl Trait for Type
, em que Trait
é o nome da característica que está sendo implementada e Type
é o nome do struct ou da enumeração do implementador.
Dentro do bloco impl
, colocamos as assinaturas de método que a definição da característica exigia, preenchendo o corpo do método com o comportamento específico que queremos que os métodos da característica tenham para o tipo específico.
Quando um tipo implementa uma determinada característica, ele promete cumprir seu contrato. Após implementar a característica, podemos chamar os métodos em instâncias de Circle
e Rectangle
, da mesma forma que chamamos métodos regulares, da seguinte maneira:
let circle = Circle { radius: 5.0 };
let rectangle = Rectangle {
width: 10.0,
height: 20.0,
};
println!("Circle area: {}", circle.area());
println!("Rectangle area: {}", rectangle.area());
Você pode interagir com esse código neste link do Rust Playground.