English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
Os generics são um mecanismo indispensável para uma linguagem de programação.
C++ A linguagem usa "template" para implementar generics, enquanto o C não tem mecanismo de generics, o que torna difícil construir projetos complexos de tipo no C.
O mecanismo de generics é usado pela linguagem de programação para expressar abstrações de tipo, geralmente usado em classes com funcionalidades determinadas e tipos de dados não determinados, como listas ligadas, tabelas de dispersão, etc.
Este é um método de seleção de ordenação para números inteiros:
fn max(array: &[i32]) -> i32 {}} let mut max_index = 0; let mut i = 1; while i < array.len() { se array[i] > array[max_index] { max_index = i; } i += 1; } array[max_index] } fn main() { let a = [2, 4, 6, 3, 1]; println!("max = {}", max(&a)); }
Resultado da Execução:
max = 6
Este é um programa simples para encontrar o maior valor, que pode ser usado para processar i32 Os dados do tipo numérico, mas não podem ser usados em f64 Os dados do tipo. Ao usar genéricos, podemos fazer com que essa função seja utilizável em vários tipos. No entanto, não todos os tipos de dados podem ser comparados, então o próximo código não é para ser executado, mas para descrever a sintaxe do genérico da função:
fn max<T>(array: &[T])}} -> T { let mut max_index = 0; let mut i = 1; while i < array.len() { se array[i] > array[max_index] { max_index = i; } i += 1; } array[max_index] }
Nos exemplos de Option e Result que estudamos anteriormente, as classes de enumeração são genéricas.
Estruturas e classes de enumeração em Rust podem implementar o mecanismo genérico.
struct Ponto<T> { x: T, y: T, }
Esta é uma estrutura de coordenadas de ponto, T representa o tipo numérico que descreve as coordenadas do ponto. Podemos usá-lo assim:
let p1 = Ponto {x: 1, y: 2}; let p2 = Ponto {x: 1.0, y: 2.0};
ao usar não há declaração de tipo, aqui está sendo usado o mecanismo de tipo automático, mas não é permitido que haja desavenças de tipo, como a seguinte:
let p = Ponto {x: 1, y: 2.0};
x e 1 ao vincular já estabelece T como i32Portanto, não é permitido que mais apareça f64 do tipo. Se quisermos que x e y sejam representados por tipos de dados diferentes, podemos usar dois identificadores genéricos:
struct Ponto<T1, T2> { x: T,1, y: T,2 }
Métodos genéricos representados em classes de enumeração como Option e Result:
enum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), }
Estruturas e classes de enumeração podem definir métodos, então os métodos também devem implementar o mecanismo genérico, senão a classe genérica não pode ser operada eficazmente por métodos.
struct Ponto<T> { x: T, y: T, } impl<T> Ponto<T> { fn x(&self) -> &T { &self.x } } fn main() { let p = Ponto { x: 1, y: 2 }; println!("p.x = {}", p.x()); }
Resultado da Execução:
p.x = 1
Atenção, o keyword impl deve ter <T> após ele, pois T é o padrão para o que está após ele. Mas também podemos adicionar métodos a um dos generic types:
impl Ponto<f64> { fn x(&self) -> f64 {}} self.x } }
A implementação genérica do bloco não impede que os métodos internos tenham capacidade genérica:
impl<T, U> Ponto<T, U> { fn mixup<V, W>(self, other: Ponto<V, W>) -> Ponto<T, W> { Ponto { x: self.x, y: other.y, } } }
O método mixup funde o valor x de um ponto Point<T, U> com o valor y de um ponto Point<V, W> em um novo ponto do tipo Point<T, W>.
A ideia da característica (trait) é semelhante à do Java (Interface), mas não são completamente idênticos. O que é comum entre eles é que ambos são um padrão de comportamento que pode ser usado para identificar quais classes têm quais métodos.
A característica é representada por trait no Rust:
trait Descriptive { fn describe(&self) -> String; }
Descriptive especifica que o implementador deve ter o método describe(&self) -> Método String.
Usamos isso para implementar uma estrutura:
struct Person { name: String, age: u8 } impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } }
O formato é:
impl <nome_da_característica> for <nome_do_tipo_que_Implementa>
Em Rust, um tipo pode implementar várias características, mas cada bloco impl pode implementar apenas uma.
Essa é a diferença entre característica e interface: o interface pode especificar métodos, mas não pode definir métodos, enquanto a característica pode definir métodos como métodos padrão, pois são "padrão", o objeto pode redefinir métodos ou usar métodos padrão sem redefinir:
trait Descriptive { fn describe(&self) -> String { String::from("[Object]") } } struct Person { name: String, age: u8 } impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } } fn main() { let cali = Person { nome: String::from("Cali"), idade: 24 }; println!("{}", cali.describe()); }
Resultado da Execução:
Cali 24
Se removermos o conteúdo do bloco impl Descriptive for Person, o resultado da execução será:
[Object]
Muitas vezes precisamos passar uma função como parâmetro, por exemplo, funções de callback, eventos de botão de configuração, etc. Em Java, a função deve ser passada por meio de uma instância de classe que implementa o interface, em Rust, isso pode ser feito passando parâmetros de características:
fn output(object: impl Descriptive) { println!("{}", object.describe()); }
Qualquer objeto que implemente a característica Descriptive pode ser passado como parâmetro para essa função, essa função não precisa saber se o objeto传入具有其他属性或 métodos, apenas que ele tem métodos normativos da característica Descriptive. Claro, também não é possível usar outras propriedades ou métodos dentro dessa função.
Os parâmetros de característica também podem ser implementados usando essa sintaxe equivalente:}}
fn output<T: Descriptive>(object: T) { println!("{}", object.describe()); }
Isso é um açúcar sintático semelhante ao genérico, que é muito útil quando vários tipos de parâmetros são características:
fn output_two<T: Descriptive>(arg1: T, arg2: T) { println!("{}", arg1.describe()); println!("{}", arg2.describe()); }
Se uma característica envolve várias características, pode usar + Símbolos, por exemplo:
fn notify(item: impl Summary + Display) fn notify<T: Summary + Display>(item: T)
Atenção:Apenas para representar tipos, não significa que podem ser usados dentro de um bloco impl.
Relações de implementação complexas podem ser simplificadas usando a palavra-chave where, por exemplo:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)
Pode ser simplificado para:
fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug
Após entender essa sintaxe, o caso de "encontrar o maior" no capítulo de genéricos pode ser realmente implementado:
trait Comparable { fn compare(&self, object: &Self) -> i8; } fn max<T: Comparable>(array: &[T]) -> &T { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i].compare(&array[max_index]) > 0 { max_index = i; } i += 1; } &array[max_index] } impl Comparable for f64 {}} fn compare(&self, object: &f64) -> i8 {}} if &self > &object { 1 } else if &self == &object { 0 } else { -1 } } } fn main() { let arr = [1.0, 3.0, 5.0, 4.0, 2.0]; println!("máximo de arr é {}", max(&arr)); }
Resultado da Execução:
máximo de arr é 5
Dica: Devido ao fato de que o segundo parâmetro da função compare deve ser o mesmo tipo que implementou a característica, a palavra-chave Self (lembre-se da capitalização) representa o tipo atual (não o exemplo) em si.
Formato de Retorno de Características:
fn pessoa() -> impl Descriptive { Pessoa { nome: String::from("Cali"), idade: 24 } }
Mas há um ponto, as características que fazem o retorno aceitam apenas objetos que implementaram a característica como retorno e todos os tipos possíveis de retorno no mesmo função devem ser completamente idênticos. Por exemplo, as estruturas A e B implementaram a característica Trait, a função a seguir é incorreta:
fn some_function(bool bl) -> impl Descriptive { if bl { return A {}; } else { return B {}; } }
A funcionalidade é muito poderosa, podemos usá-la para implementar métodos de classes. Mas para classes genéricas, às vezes precisamos distinguir os métodos implementados pelo generics para decidir qual método implementar a seguir:
struct A<T> {} impl<T: B + C> A<T> { fn d(&self) {} }
Este código declara que o tipo A<T> deve estar implementado previamente com as características B e C para que o bloco impl seja eficaz.