Rust 特质边界是否应该在struct和impl中重复?

Rust 特质边界是否应该在struct和impl中重复?,rust,Rust,以下代码使用泛型类型的结构。虽然它的实现只对给定的trait绑定有效,但可以使用或不使用相同的绑定来定义结构。结构的字段是私有的,因此没有其他代码可以创建实例 trait Trait { fn foo(&self); } struct Object<T: Trait> { value: T, } impl<T: Trait> Object<T> { fn bar(object: Object<T>) {

以下代码使用泛型类型的结构。虽然它的实现只对给定的trait绑定有效,但可以使用或不使用相同的绑定来定义结构。结构的字段是私有的,因此没有其他代码可以创建实例

trait Trait {
    fn foo(&self);
}

struct Object<T: Trait> {
    value: T,
}

impl<T: Trait> Object<T> {
    fn bar(object: Object<T>) {
        object.value.foo();
    }
}
trait{
fn foo(和self);
}
结构对象{
值:T,
}
impl对象{
fn条(对象:对象){
object.value.foo();
}
}

为了符合干燥原理,应该省略结构的特性,还是应该给出它以澄清依赖性?或者,在某些情况下,一种解决方案应该优先于另一种解决方案?

这实际上取决于该解决方案的类型。如果它只打算保存实现特征的值,那么是的,它应该具有特征绑定,例如

trait Child {
    fn name(&self);
}

struct School<T: Child> {
    pupil: T,
}

impl<T: Child> School<T> {
    fn role_call(&self) -> bool {
        // check everyone is here
    }
}
trait-Child{
fn名称(&self);
}
结构学校{
学生:T,
}
impl学校{
fn角色调用(&self)->bool{
//检查一下,所有人都到了
}
}
在本例中,学校只允许孩子入学,因此我们在结构上有了边界

如果该结构打算保存任何值,但您希望在实现trait时提供额外的行为,那么不,绑定不应该在该结构上,例如

trait GoldCustomer {
    fn get_store_points(&self) -> i32;
}

struct Store<T> {
    customer: T,
}

impl<T: GoldCustomer> Store {
    fn choose_reward(customer: T) {
        // Do something with the store points
    }
}
trait黄金客户{
fn获取存储点(&self)->i32;
}
结构存储{
顾客:T,
}
impl商店{
fn选择奖励(客户:T){
//对存储点做些什么
}
}

在本例中,并非所有客户都是黄金客户,在结构上设置边界是没有意义的。

应用于结构的每个实例的Trait边界应应用于结构:

struct IteratorThing<I>
where
    I: Iterator,
{
    a: I,
    b: Option<I::Item>,
}
符合干燥原则

冗余将通过以下方式消除:

消除对函数和impl的“冗余”边界的需要 这些界限可以从输入类型和其他特征推断出来 界限。例如,在这个简单的程序中,impl将不再 需要绑定,因为它可以从
Foo
类型推断:

struct Foo<T: Debug> { .. }
impl<T: Debug> Foo<T> {
  //    ^^^^^ this bound is redundant
  ...
}
struct Foo{..}
impl-Foo{
//^^^^^^此绑定是多余的
...
}

我相信现有的答案是误导性的。在大多数情况下,您不应该在结构上设置一个绑定,除非该结构没有绑定就无法编译

我会解释的,但首先,让我们把一件事弄清楚:这不是关于减少击键。目前,在Rust中,您必须在每个触及它的
impl
上重复每个结构的边界,这是现在不在结构上设置边界的充分理由。然而,这不是我建议从结构中省略特征边界的理由。
隐含的\u界限
RFC最终将被实现,但我仍然建议不要在结构上设置界限


tl;博士 结构上的边界对大多数人来说是错误的。它们具有传染性、冗余性、有时近视,并且常常令人困惑。即使你觉得绑定是对的,你通常也应该在证明有必要之前停止绑定

(在这个答案中,我所说的关于结构的任何内容都同样适用于枚举。)


1.结构的边界从抽象中泄漏出来。 您的数据结构是特殊的。“只有当
T
Trait
时,
Object
才有意义,”你说。也许你是对的。但决策不仅影响
对象
,还影响包含
对象
的任何其他数据结构,即使它并不总是包含
对象
。考虑一个想在<代码> EnUM <代码> > < /P>中包装<代码>对象<代码>的程序员
enum MyThing<T> {  // error[E0277]: the trait bound `T: Trait` is not satisfied
    Wrapped(your::Object<T>),
    Plain(T),
}
enum MyThing{//error[E0277]:不满足特性绑定'T:trait'
包装(您的::对象),
平原(T),
}
在下游代码中,这是有意义的,因为
MyThing::Wrapped
仅用于实现
东西的
T
s,而
Plain
可用于任何类型。但是如果
your::Object
T
上有一个绑定,那么如果没有这个绑定,这个
enum
就无法编译,即使
Plain(T)
有很多不需要这样一个绑定的用途。这不仅不起作用,而且即使添加绑定并不会使其完全无用,它也会在任何碰巧使用
虚构
的结构的公共API中公开绑定

结构的边界限制了其他人可以使用它们做什么。当然,代码的边界(
impl
s和函数)也是如此,但这些约束(大概)是您自己的代码所必需的,而结构的边界则是对下游任何可能以创新方式使用您的结构的人的先发制人的攻击。这可能是有用的,但不必要的限制对这些创新者来说尤其恼人,因为它们限制了可以编译的内容,而没有有效地限制实际可以运行的内容(稍后将详细介绍)

2.结构上的边界与代码上的边界是冗余的。 那么你认为下游创新是不可能的?这并不意味着结构本身需要一个绑定。为了使在没有
T:Trait
的情况下不可能构造
对象
,只需在包含
对象的构造函数的
impl
上加上该边界就足够了;如果没有
T:Trait
就无法在
对象上调用
a_方法
,您可以在包含
a_方法
impl
上或者在
a_方法
本身上这样说。(在
implicated_bounds
实现之前,无论如何,你都必须这样做,这样你就连“保存击键”的理由都没有了。但这最终会改变。)

即使,特别是当你想不出任何方法让下游使用一个无边界的
对象时,你也不应该事先禁止它,因为

3.结构上的边界实际上意味着dif
enum MyThing<T> {  // error[E0277]: the trait bound `T: Trait` is not satisfied
    Wrapped(your::Object<T>),
    Plain(T),
}