rust 如何从trait方法返回&amp;T或Ref< T>

nx7onnlm  于 2023-11-19  发布在  其他
关注(0)|答案(1)|浏览(116)

我试图创建一个trait,它 * 返回一个字符串的引用 *。
在一个实现trait的结构中,我持有一个String
在另一个实现trait的结构中,我持有一个RefCell<String>。从这里我可以返回一个Ref<str>
我发现的唯一一个可以同时容纳这两种类型的签名如下(A boxed Deref trait):

trait Get<'a> {
        fn get(&'a self) -> Box<dyn Deref<Target=str> + 'a>;
    }

个字符
这不仅是令人难以置信的丑陋,而且现在我还需要调用deref()两次,一次是Box,一次是trait

#[test]
fn test_ref() {
        let a = A {
            name: "Woolly".to_string()
        };
        let b = B {
            name: RefCell::new("Woolly".to_string())
        };
        assert_eq!(a.get().deref().deref(), "Woolly");
        assert_eq!(b.get().deref().deref(), "Woolly");
    }


有没有其他方法可以做到这一点?
Playground

dsekswqp

dsekswqp1#

您可以引入一个关联类型来用作返回值。

trait Get {
    type Ret<'a>: Deref<Target = str> + 'a
    where
        Self: 'a;
    fn get<'a>(&'a self) -> Self::Ret<'a>;
}

字符串
代码的其余部分可以这样修改。

struct A {
    name: String,
}

impl Get for A {
    type Ret<'a> = &'a str;
    fn get<'a>(&'a self) -> Self::Ret<'a> {
        self.name.as_str()
    }
}

struct B {
    name: RefCell<String>,
}

impl Get for B {
    type Ret<'a> = Ref<'a, str>;
    fn get<'a>(&'a self) -> Self::Ret<'a> {
        Ref::map(self.name.borrow(), |it| it.as_str())
    }
}

#[test]
fn test_ref() {
    let a = A {
        name: "Woolly".to_string(),
    };
    let b = B {
        name: RefCell::new("Woolly".to_string()),
    };
    assert_eq!(&*a.get(), "Woolly");
    assert_eq!(&*b.get(), "Woolly");
}


这适用于给定的测试,通常应该足够了,但是如果你想像这个测试一样统一类型:

#[test]
fn test_ref2() {
    let a = A {
        name: "Woolly".to_string(),
    };
    let b = B {
        name: RefCell::new("Woolly".to_string()),
    };

    // error[E0308]: `if` and `else` have incompatible types
    let deref_str = if rand::random() {
        a.get()
    } else {
        b.get()
    };
    
    assert_eq!(&*deref_str, "Woolly");
}


然后你需要重新引入trait对象或枚举。

#[test]
fn test_ref2() {
    let a = A {
        name: "Woolly".to_string(),
    };
    let b = B {
        name: RefCell::new("Woolly".to_string()),
    };

    let deref_str: Box<dyn Deref<Target = str>> = if rand::random() {
        Box::new(a.get())
    } else {
        Box::new(b.get())
    };

    assert_eq!(&**deref_str, "Woolly");
}

#[test]
fn test_ref3() {
    enum StrOrRef<'a> {
        Str(&'a str),
        Ref(Ref<'a, str>),
    }

    impl Deref for StrOrRef<'_> {
        type Target = str;
        fn deref(&self) -> &str {
            match self {
                Self::Str(s) => s,
                Self::Ref(r) => &*r,
            }
        }
    }

    let a = A {
        name: "Woolly".to_string(),
    };
    let b = B {
        name: RefCell::new("Woolly".to_string()),
    };

    let deref_str = if rand::random() {
        StrOrRef::Str(a.get())
    } else {
        StrOrRef::Ref(b.get())
    };

    assert_eq!(&*deref_str, "Woolly");
}


你可以做的另一件事是将AB的impls改为type Ret<'a> = StrOrRef<'a>
任何一个都可能比让trait方法返回Box更好,因为它允许trait是灵活的。
(playground)

相关问题