Como obter uma referência de estrutura de uma característica em checkbox?

Como obtenho Box ou &B ou &Box da variável neste código:

 trait A {} struct B; impl A for B {} fn main() { let mut a: Box = Box::new(B); let b = a as Box; } 

Este código retorna um erro:

 non-scalar cast: Box as Box 

Existem duas maneiras de fazer downcasting em Rust. O primeiro é usar Any . Observe que isso permite que você reduza para o tipo exato e original de concreto. Igual a:

 use std::any::Any; trait A { fn as_any(&self) -> &Any; } struct B; impl A for B { fn as_any(&self) -> &Any { self } } fn main() { let a: Box = Box::new(B); // The indirection through `as_any` is because using `downcast_ref` // on `Box` *directly* only lets us downcast back to `&A` again. // The method ensures we get an `Any` vtable that lets us downcast // back to the original, concrete type. let b: &B = match a.as_any().downcast_ref::() { Some(b) => b, None => panic!("&a isn't a B!") }; } 

A outra maneira é implementar um método para cada “alvo” no traço base (neste caso, A ) e implementar os lançamentos para cada tipo de destino desejado.


Espere, por que precisamos de as_any ?

Mesmo se você adicionar Any como um requisito para A , ainda não funcionará corretamente. O primeiro problema é que o A in Box também implementará Any … significando que quando você chama downcast_ref , na verdade você estará chamando-o no tipo de object A Any pode apenas baixar para o tipo em que foi invocado, que neste caso é A , então você só poderá voltar para &A que você já tinha.

Mas há uma implementação de Any para o tipo subjacente em algum lugar , certo? Bem, sim, mas você não pode fazer isso. Rust não permite “cruzar” de &A para &Any .

É para isso que as_any porque é algo implementado apenas em nossos tipos “concretos”, o compilador não fica confuso sobre qual deles deve invocar. Chamá-lo em um &A faz com que ele despache dinamicamente para a implementação concreta (novamente, neste caso, B::as_any ), que retorna um &Any usando a implementação de Any for B , que é o que queremos.

Note que você pode evitar todo esse problema simplesmente não usando A Especificamente, o seguinte também funcionará:

 fn main() { let a: Box = Box::new(B); let _: &B = match a.downcast_ref::() { Some(b) => b, None => panic!("&a isn't a B!") }; } 

No entanto, isso impede que você tenha outros methods; tudo que você pode fazer aqui é reduzido a um tipo concreto.

Como uma nota final de interesse potencial, a checkbox da mopa permite combinar a funcionalidade de Any com uma característica própria.

Deve ficar claro que o lançamento pode falhar se houver outro tipo C implementando A e você tentar conjurar o Box em um Box . Eu não sei sua situação, mas para mim parece muito que você está trazendo técnicas de outras linguagens, como Java, para o Rust. Eu nunca encontrei esse tipo de problema em Rust – talvez seu design de código pudesse ser melhorado para evitar esse tipo de conversão.

Se você quiser, você pode “converter” praticamente qualquer coisa com mem::transmute . Infelizmente, teremos um problema se apenas quisermos conjurar Box para Box ou &A para &B porque um ponteiro para uma trait é um ponteiro de gordura que na verdade consiste em dois pointers: Um para o object real, um para o vptr. Se estamos lançando para um tipo struct , podemos simplesmente ignorar o vptr. Por favor, lembre-se que esta solução é altamente insegura e bastante hacky – eu não a usaria em código “real”.

 let (b, vptr): (Box, *const ()) = unsafe { std::mem::transmute(a) }; 

EDIT: Screw isso, é ainda mais inseguro do que eu pensava. Se você quiser fazer isso corretamente, você teria que usar std::raw::TraitObject . Isso ainda é instável embora. Eu não acho que isso seja de alguma utilidade para o OP; não use!

Existem alternativas melhores nesta questão muito semelhante: Como combinar implementadores de traços