在 rust 中,您可以擁有一個特征,將其實作為一個結構,然后將您的結構向上轉換為一個特征物件:
trait T {}
struct S {}
impl T for S {}
fn main() {
let s: S = S {};
let s_as_t: &dyn T = &s;
}
這是一個非常有用的功能,因為如果我有多個物件都實作了 trait T,我現在可以將它們全部放在一個 type 陣列中Vec<Box<dyn T>>,并通過在每個元素上呼叫一個函式來非常容易地定義全域行為。
但
當我的原始特征也有關聯型別時,我該如何做同樣的事情?
這非常有效,沒有 pb :
trait T_Subtype {}
trait T {
type subtype: T_Subtype;
}
struct S {}
impl T_Subtype for S {}
impl T for S {
type subtype = S;
}
fn main() {
let s: S = S {};
let s_as_t: &dyn T<subtype = S> = &s;
}
但我找不到任何方法來向上轉換關聯型別,以下代碼無法編譯:
trait T_Subtype {}
trait T {
type subtype: T_Subtype;
}
struct S {}
impl T_Subtype for S {}
impl T for S {
type subtype = S;
}
fn main() {
let s: S = S {};
let s_as_t: &dyn T<subtype = dyn T_Subtype> = &s; // only line that changes
}
如果沒有這個特性,我不能把(這是一個插圖)多個結構S1 S2和S3,所有實作T但可能有不同的子型別,在一個陣列中,我必須為每個子型別定義全域行為,這使得它很難維護(特別是如果有多個子型別),即使我想在所有子型別上呼叫的函式都已定義!
uj5u.com熱心網友回復:
這是不可能的。
考慮以下代碼:
trait SubtypeTrait {}
trait T {
type Subtype: SubtypeTrait;
fn foo(arg: &<Self as T>::Subtype);
}
struct S {
val: i32,
}
impl SubtypeTrait for S {}
impl T for S {
type Subtype = S;
fn foo(arg: &S) {
println!("{}", arg.val);
}
}
struct X {}
impl SubtypeTrait for X {}
impl T for X {
type Subtype = S;
fn foo(arg: &S) {
println!("{}", arg.val);
}
}
// Everything compiles except `main`
fn main() {
let x: X = X {};
let x_as_t: &dyn T<Subtype = dyn SubtypeTrait> = &x;
let s: S = S {};
// Attempts to access `arg.val`...
// but `x` doesn't have `val`!
x_as_t.foo(&x);
}
問題在于,系結在占位符型別上的特征不會限制對占位符型別的操作,它只會限制型別本身。指定型別后,您可以從所需的型別中選擇任何操作。
uj5u.com熱心網友回復:
好的,所以這個問題讓我的大腦受傷了一段時間。顯然這是不可能的,但我仍然覺得應該有辦法。這里是:
// we define the traits and sub-traits, like we did before
trait T2 {
fn hello2(&self);
}
trait T1 {
type SubT: T2;
fn hello1(&self) -> Self::SubT;
}
// first implementation of T1 (maybe somewhere else in the code)
struct S1;
impl T2 for S1 {
fn hello2(&self) {
println!("hello2 from S1");
}
}
impl T1 for S1 {
type SubT = S1;
fn hello1(&self) -> Self::SubT {
println!("hello from s1");
S1 {}
}
}
// second implementation of T1 (maybe somewhere else in the code)
struct S2;
impl T2 for S2 {
fn hello2(&self) {
println!("hello2 from S2");
}
}
impl T1 for S2 {
type SubT = S2;
fn hello1(&self) -> Self::SubT {
println!("hello from s2");
S2 {}
}
}
// where the magic happens !!
// we use a blanket implementation to make it automatic
trait T1Blanket {
fn hello1(&self) -> Box<dyn T2>;
}
impl<S: T1> T1Blanket for S where S::SubT: 'static {
fn hello1(&self) -> Box<dyn T2> {
Box::from(self.hello1()) as Box<dyn T2>
}
}
// and now we can use it easily
fn main() {
let s1 = S1 {};
let s2 = S2 {};
let array: Vec<Box<dyn T1Blanket>> = vec![Box::from(s1), Box::from(s2)];
for element in array {
let h = element.hello1(); // prints "hello from s1" and "hello from s2" successively
h.hello2(); // you can also call the other trait method's
}
}
這很好用,最好的部分是一旦你創建了毯子實作,你就不必碰任何東西了!您可以根據需要創建任意數量的實作結構,T1并且T2它們不會受到任何限制(它們也不需要實作特殊功能)。
如果你有很多這樣的特性,你甚至可以使用宏來自動化這項作業:這個 crate顯然是為你做的。
我仍然非常驚訝的是,這并沒有直接包含在具有關聯型別的 trait 物件的默認行為中。只要呼叫函式不需要特征實作,使用特征物件應該不是問題。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/519883.html
上一篇:Kotlin-Spring-如何將帶有泛型的介面作為引數傳遞給Mockito.any()
下一篇:Swift-注入和存盤協議
