避免对结构体构造函数使用显式类型注释,其中结构体包含通用字段

问题描述 投票:0回答:1

我正在尝试创建一个包含实现特征的结构,该特征可以是自有类型或引用计数指针,例如

Arc
Rc
等。到目前为止,我已经使用
 实现了以下内容然而,Borrow
特性,编译器强制我注释
new
函数返回的实例的类型,这对于我的板条箱用户来说并不理想。

如何修改下面的代码,让用户不需要注释

new
函数返回的实例的类型?

struct Foo<B, T> {
    borrow: B,
    _marker: std::marker::PhantomData<T>
}

impl<B, T> Foo<B, T> {
    fn new(borrow: B) -> Self {
        Self {
            borrow,
            _marker: Default::default()
        }
    }
}

impl <B, T> Foo<B, T> where
    B: std::borrow::Borrow<T>,
    T: std::fmt::Display {
    fn print(&self) {
        println!("{}", self.borrow.borrow())
    }
}

fn main() {
    // owned string (remove type annotation for error)
    let f: Foo<_, String> =
        Foo::new(String::from("hello"));
    f.print();

    // string slice (remove type annotation for error)
    let f: Foo<_, &str> =
        Foo::new("hello");
    f.print();

    // string in Arc (remove type annotation for error)
    let f: Foo<_, std::sync::Arc<String>> =
        Foo::new(std::sync::Arc::new(String::from("hello")));
    f.print();
}

generics rust ownership
1个回答
0
投票

您的代码的问题在于,您的

B
可以为许多不同的
Borrow<T>
类型实现
T
,因此,如果您不以某种方式指定
T
,则调用将不明确。
AsRef
也是如此。

但这不是为了

Deref
。每个类型只能实现一次,并且已经为任何
std
智能指针实现了。

代码很简单,因为您可以约束

Deref::Target
类型:

use std::ops::Deref;

impl<B> Foo<B>
where
    B: Deref,
    <B as Deref>::Target: std::fmt::Display,
{
    fn print(&self) {
        println!("{}", self.obj.deref());
    }
}

这有一个缺点,只适用于类似智能指针的类型。也就是说,如果

B = i32
它将不起作用,因为诸如
i32
之类的普通类型无法实现
Deref
:

    let f: Foo<_> = Foo::new(42);
    f.print();

> error[E0599]: the method `print` exists for struct `Foo<{integer}>`, but its > trait bounds were not satisfied
> note: trait bound `{integer}: Deref` was not satisfied
> where
>      B: Deref,
>         ^^^^^ unsatisfied trait bound introduced here

如果您觉得可以,您可以在这里停止阅读。

如果这不行,我认为最好的选择是忘记

AsRef/Deref/Borrow
的事情,只要求
B
实现你想要的特征,或者该特征的外观。

具体细节会根据您想要实现的具体内容而有所不同。如果你的特质实际上是

Display
并且不是占位符,那么你实际上不需要任何特殊的东西,因为
Display
是为
Box<T>
Rc<T>
Arc<T>
等实现的,当
T: Display
时。所以你只需写:

impl<B> Foo<B>
where B: std::fmt::Display,
{
    fn print(&self) {
        println!("{}", self.borrow)
    }
}

但我猜你的实际特质不是

Display
。同样,细节会根据您是否拥有该特征而有所不同。想象一下这个特征:

trait Printable {
    fn print(&self);
}
impl Printable for str {
    fn print(&self) { println!("s:{}", self); }
}
impl Printable for i32 {
    fn print(&self) { println!("i:{}", self); }
}

如果是你的,你可以直接编写以下代码,但如果是第三方的,你将需要一个 facade 特征:

trait AsPrintable {
    type Target: Printable + ?Sized;
    fn as_printable(&self) -> &Self::Target;
}

你的

Foo
将是:

impl<B> Foo<B>
where
    B: AsPrintable,
{
    fn print(&self) {
        self.obj.as_printable().print();
    }
}

然后您可以为您想要的任何类型实现该特征。这里的小不便之处在于您需要两个编写两个毯子实现:

impl<T: Printable> AsPrintable for T {
    type Target = T;
    fn as_printable(&self) -> &T { self }
}
impl<T: Deref> AsPrintable for T
    where <T as Deref>::Target: AsPrintable
{
    type Target = <<T as Deref>::Target as AsPrintable>::Target;
    fn as_printable(&self) -> &Self::Target { self.deref().as_display() }
}

但是只允许其中之一,Rustc 由于不明确而不会同时允许两者。如果需要,可以通过对所需包装器进行一些手动实现来修复此问题。或者用新类型来替换第一个毯子实现,例如这个:

struct MakePrintable<T>(T);

impl<T: Printable> AsPrintable for MakePrintable<T> {
    type Target = T;
    fn as_printable(&self) -> &T { &self.0 }
}

这是一个包含整个示例的游乐场

© www.soinside.com 2019 - 2024. All rights reserved.