我有一个 struct MyStruct,它接受一个通用参数T: SomeTrait,我想为MyStruct实现一个new方法.这是有效的:

/// Constraint for the type parameter `T` in MyStruct
pub trait SomeTrait: Clone {}

/// The struct that I want to construct with `new`
pub struct MyStruct<T: SomeTrait> {
    value: T,
}

fn new<T: SomeTrait>(t: T) -> MyStruct<T> {
    MyStruct { value: t }
}

fn main() {}

我想把new函数放在impl块中,如下所示:

impl MyStruct {
    fn new<T: SomeTrait>(t: T) -> MyStruct<T> {
        MyStruct { value: t }
    }
}

但这并不能用:

error[E0107]: wrong number of type arguments: expected 1, found 0
 --> src/main.rs:9:6
  |
9 | impl MyStruct {
  |      ^^^^^^^^ expected 1 type argument

如果我试着这样说:

impl MyStruct<T> {
    fn new(t: T) -> MyStruct<T> {
        MyStruct { value: t }
    }
}

错误更改为:

error[E0412]: cannot find type `T` in this scope
 --> src/main.rs:9:15
  |
9 | impl MyStruct<T> {
  |               ^ not found in this scope

如何提供通用 struct 的实现?我应该把通用参数及其约束放在哪里?

推荐答案

类型参数<T: SomeTrait>应该紧跟在impl关键字之后:

impl<T: SomeTrait> MyStruct<T> {
    fn new(t: T) -> Self {
        MyStruct { value: t }
    }
}

如果impl<...>中的类型和约束列表过长,可以使用where语法并单独列出约束:

impl<T> MyStruct<T>
where
    T: SomeTrait,
{
    fn new(t: T) -> Self {
        MyStruct { value: t }
    }
}

请注意Self的用法,这是impl块中MyStruct<T>的快捷方式.


Remarks

  1. this answer中解释了为什么需要impl<T>.从本质上讲,它归结为一个事实,即impl<T> MyStruct<T>impl MyStruct<T>都是有效的,但意味着不同的东西.

  2. 当您将new移动到impl块中时,应该删除多余的类型参数,否则 struct 的接口将变得不可用,如下例所示:

    // trait SomeTrait and struct MyStruct as above
    // [...]
    
    impl<T> MyStruct<T>
    where
        T: SomeTrait,
    {
        fn new<S: SomeTrait>(t: S) -> MyStruct<S> {
            MyStruct { value: t }
        }
    }
    
    impl SomeTrait for u64 {}
    impl SomeTrait for u128 {}
    
    fn main() {
        // just a demo of problematic code, don't do this!
        let a: MyStruct<u128> = MyStruct::<u64>::new::<u128>(1234);
        //                                 ^
        //                                 |
        //        This is an irrelevant type
        //        that cannot be inferred. Not only will the compiler
        //        force you to provide an irrelevant type, it will also
        //        not prevent you from passing incoherent junk as type
        //        argument, as this example demonstrates. This happens 
        //        because `S` and `T` are completely unrelated.
    }
    

Rust相关问答推荐

为什么拥有的trait对象的相等运算符移动了正确的操作数?

空字符串转换为Box字符串时是否分配?<>

Rust中的相互递归特性与默认实现

当Option为None时,Option数组是否占用Rust中的内存?

在自身功能上实现类似移动的行为,以允许通过大小的所有者进行呼叫(&;mut;self)?

在Rust中,如果Result是Err,运行副作用(如日志(log)记录)的惯用方法是什么

有没有办法避免在While循环中多次borrow `*分支`

如何为 struct 字段设置新值并在Ruust中的可变方法中返回旧值

使用Py03从Rust调用Python函数时的最佳返回类型

`Pin`有没有不涉及不安全代码的目的?

如何从ruust中的fig.toml中读取?

由于生存期原因,返回引用的闭包未编译

为什么编译器看不到这个 `From` impl?

根据掩码将 simd 通道设置为 0 的惯用方法?

返回优化后的标题:返回异步块的闭包的类型擦除

为什么在 macOS / iOS 上切换 WiFi 网络时 reqwest 响应会挂起?

为什么指定生命周期让我返回一个引用?

具有生命周期和以后引用的可变方法

实现不消费的迭代器

如何从 Rust 中不同类型的多个部分加入 Path?