简体   繁体   English

是否应该在 struct 和 impl 中重复 trait bound?

[英]Should trait bounds be duplicated in struct and impl?

The following code uses a struct with generic type.以下代码使用具有泛型类型的结构。 While its implementation is only valid for the given trait bound, the struct can be defined with or without the same bound.虽然它的实现只对给定的 trait bound 有效,但结构可以在有或没有相同边界的情况下定义。 The struct's fields are private so no other code could create an instance anyway.该结构的字段是私有的,因此没有其他代码可以创建实例。

trait Trait {
    fn foo(&self);
}

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

impl<T: Trait> Object<T> {
    fn bar(object: Object<T>) {
        object.value.foo();
    }
}

Should the trait bound for the structure should be omitted to conform to the DRY principle, or should it be given to clarify the dependency?应该省略结构绑定的特征以符合 DRY 原则,还是应该给出它以澄清依赖关系? Or are there circumstances one solution should be preferred over the other?或者在某些情况下,一种解决方案应该优于另一种解决方案?

I believe that the existing answers are misleading.我认为现有的答案具有误导性。 In most cases, you should not put a bound on a struct unless the struct literally will not compile without it.在大多数情况下,您不应该对结构进行限制,除非该结构没有它就无法编译

I'll explain, but first, let's get one thing out of the way: this is not about reducing keystrokes.我会解释一下,但首先,让我们弄清楚一件事:这不是为了减少击键次数。 Currently in Rust you have to repeat every struct's bounds on every impl that touches it, which is a good enough reason not to put bounds on structs right now .目前在 Rust 中,你必须在每个接触它的impl上重复每个结构的边界,这是一个足够好的理由不立即对结构设置边界。 However, this is not my reasoning for recommending to omit trait bounds from structs.但是,这不是我建议从结构中省略特征边界的理由。 The implied_bounds RFC will eventually be implemented, but I will still recommend not putting bounds on structs. implied_bounds RFC 最终将被实现,但我仍然建议不要对结构设置边界。


tl;dr tl;博士

Bounds on structs express the wrong thing for most people.对于大多数人来说,结构上的界限表达了错误的东西。 They are infectious, redundant, sometimes nearsighted, and often confusing.它们具有传染性、多余的、有时是近视的,而且常常令人困惑。 Even when a bound feels right, you should usually leave it off until it's proven necessary.即使绑定感觉正确,您通常也应该将其关闭,直到证明有必要为止。

(In this answer, anything I say about structs applies equally to enums.) (在这个答案中,我所说的关于结构的任何内容都同样适用于枚举。)


1. Bounds on structs leak out of abstractions. 1. 结构的边界从抽象中泄露出来。

Your data structure is special.你的数据结构很特别。 " Object<T> only makes sense if T is Trait ," you say. Object<T>只有在TTrait时才有意义,”你说。 And perhaps you are right.也许你是对的。 But the decision affects not just Object , but any other data structure that contains an Object<T> , even if it does not always contain an Object<T> .但该决定不仅影响Object ,还影响包含Object<T>的任何其他数据结构,即使它并不总是包含Object<T> Consider a programmer who wants to wrap your Object in an enum :考虑一个想要将您的Object包装在enum中的程序员:

enum MyThing<T> {  // error[E0277]: the trait bound `T: Trait` is not satisfied
    Wrapped(your::Object<T>),
    Plain(T),
}

Within the downstream code this makes sense because MyThing::Wrapped is only used with T s that do implement Thing , while Plain can be used with any type.在下游代码中这是有道理的,因为MyThing::Wrapped仅与实现ThingT一起使用,而Plain可以与任何类型一起使用。 But if your::Object<T> has a bound on T , this enum can't be compiled without that same bound, even if there are lots of uses for a Plain(T) that don't require such a bound.但是如果your::Object<T>T上有一个界限,那么这个enum不能在没有相同界限的情况下编译,即使有很多不需要这种界限的Plain(T)用途。 Not only does this not work, but even if adding the bound doesn't make it entirely useless, it also exposes the bound in the public API of any struct that happens to use MyThing .这不仅不起作用,而且即使添加绑定不会使其完全无用,它也会在碰巧使用MyThing的任何结构的公共 API 中公开绑定。

Bounds on structs limit what other people can do with them.结构的界限限制了其他人可以用它们做什么。 Bounds on code ( impl s and functions) do too, of course, but those constraints are (presumably) required by your own code, while bounds on structs are a preemptive strike against anyone downstream who might use your struct in an innovative way.当然,代码边界( impl和函数)也是如此,但这些约束(可能)是您自己的代码所需要的,而结构边界是对下游任何可能以创新方式使用您的结构的人的先发制人的打击。 This may be useful, but unnecessary bounds are particularly annoying for such innovators because they constrain what can compile without usefully constraining what can actually run (more on that in a moment).这可能很有用,但对于这些创新者来说,不必要的界限特别烦人,因为它们限制了可以编译的内容,而没有有效地限制可以实际运行的内容(稍后会详细介绍)。

2. Bounds on structs are redundant with bounds on code. 2. 结构的边界与代码的边界是多余的。

So you don't think downstream innovation is possible?所以你不认为下游创新是可能的吗? That doesn't mean the struct itself needs a bound.这并不意味着结构本身需要一个界限。 To make it impossible to construct an Object<T> without T: Trait , it is enough to put that bound on the impl that contains Object 's constructor (s);为了使没有T: TraitObject<T>无法构造,只需将该边界放在包含Object构造函数impl上就足够了; if it's impossible to call a_method on an Object<T> without T: Trait you can say that on the impl that contains a_method , or perhaps on a_method itself.如果不可能在没有T: TraitObject<T>上调用a_method ,则可以在包含a_methodimpla_method本身上说。 (Until implied_bounds is implemented, you have to, anyway, so you don't even have the weak justification of "saving keystrokes." But that'll change eventually.) (在implied_bounds实现之前,无论如何你都必须这样做,所以你甚至没有“保存击键”的弱理由。但这最终会改变。)

Even and especially when you can't think of any way for downstream to use an un-bounded Object<T> , you should not forbid it a priori , because...即使特别是当您想不出任何方式让下游使用无界Object<T>时,您也不应该先验地禁止它,因为...

3. Bounds on structs actually mean something different than bounds on code. 3. 结构上的界限实际上意味着与代码上的界限不同的东西。

A T: Trait bound on Object<T> means more than "all Object<T> s have to have T: Trait "; A T: Trait bound on Object<T>意味着不仅仅是“所有Object<T>都必须有T: Trait ”; it actually means something like "the concept of Object<T> itself does not make sense unless T: Trait ", which is a more abstract idea.它实际上意味着类似“ Object<T>的概念本身没有意义,除非T: Trait ”,这是一个更抽象的想法。 Think about natural language: I've never seen a purple elephant, but I can easily name the concept of "purple elephant" despite the fact that it corresponds to no real-world animal.想想自然语言:我从未见过紫色大象,但我可以轻松命名“紫色大象”的概念,尽管它不对应于现实世界的动物。 Types are a kind of language and it can make sense to refer to the idea of Elephant<Purple> , even when you don't know how to create one and you certainly have no use for one.类型是一种语言,参考Elephant<Purple>的想法是有意义的,即使您不知道如何创建类型并且您肯定没有使用它。 Similarly, it can make sense to express the type Object<NotTrait> in the abstract even if you don't and can't have one in hand right now.同样,在抽象中表达类型Object<NotTrait>也是有意义的,即使您现在没有也不能拥有一个。 Especially when NotTrait is a type parameter, which may not be known in this context to implement Trait but in some other context does.特别是当NotTrait是一个类型参数时,在这种情况下可能不知道要实现Trait ,但在其他一些情况下却可以。

Case study: Cell<T>案例研究: Cell<T>

For one example of a struct that originally had a trait bound which was eventually removed, look no farther than Cell<T> , which originally had a T: Copy bound.对于最初具有最终被删除的特征绑定的结构的一个示例,请看Cell<T> ,它最初具有T: Copy绑定。 In the RFC to remove the bound many people initially made the same kinds of arguments you may be thinking of right now, but the eventual consensus was that " Cell requires Copy " was always the wrong way to think about Cell .RFC 中,许多人最初提出了与您现在可能想到的相同类型的论点,但最终的共识是“ Cell需要Copy始终是思考Cell的错误方式。 The RFC was merged, paving the way for innovations like Cell::as_slice_of_cells , which lets you do things you couldn't before in safe code, including temporarily opt-in to shared mutation . RFC 被合并,为Cell::as_slice_of_cells之类的创新铺平了道路,它让您可以在安全代码中做以前无法做的事情,包括临时选择共享突变 The point is that T: Copy was never a useful bound on Cell<T> , and it would have done no harm (and possibly some good) to leave it off from the beginning.关键是T: CopyCell<T>上从来都不是一个有用的界限,从一开始就将其关闭不会造成任何伤害(并且可能有一些好处)。

This kind of abstract constraint can be hard to wrap one's head around, which is probably one reason why it's so often misused.这种抽象的约束很难让人理解,这可能是它经常被滥用的原因之一。 Which relates to my last point:这与我的最后一点有关:

4. Unnecessary bounds invite unnecessary parameters (which are worse). 4. 不必要的界限会引入不必要的参数(更糟)。

This does not apply to all cases of bounds on structs, but it is a common point of confusion.这并不适用于结构边界的所有情况,但这是一个常见的混淆点。 You may, for instance, have a struct with a type parameter that must implement a generic trait, but not know what parameter(s) the trait should take.例如,您可能有一个带有类型参数的结构,该结构必须实现一个泛型特征,但不知道该特征应该采用什么参数。 In such cases it is tempting to use PhantomData to add a type parameter to the main struct, but this is usually a mistake, not least because PhantomData is hard to use correctly.在这种情况下,很容易使用PhantomData向主结构添加类型参数,但这通常是一个错误,尤其是因为PhantomData很难正确使用。 Here are some examples of unnecessary parameters added because of unnecessary bounds: 1 2 3 4 5 In the majority of such cases, the correct solution is simply to remove the bound.以下是一些由于不必要的界限而添加的不必要参数的示例: 1 2 3 4 5在大多数此类情况下,正确的解决方案是简单地删除界限。

Exceptions to the rule规则的例外

Okay, when do you need a bound on a struct?好的,什么时候需要对结构进行绑定? I can think of two reasons.我可以想到两个原因。 In Shepmaster's answer , the struct will simply not compile without a bound, because the Iterator implementation for I actually defines what the struct contains;Shepmaster 的回答中,该结构将根本无法无限制地编译,因为IIterator实现实际上定义了该结构包含的内容; it's not just an arbitrary rule.这不仅仅是一个武断的规则。 Also, if you're writing unsafe code and you want it to rely on a bound ( T: Send , for example), you might need to put that bound on the struct.此外,如果您正在编写unsafe的代码并且希望它依赖于绑定(例如T: Send ),您可能需要将该绑定放在结构上。 unsafe code is special because it can rely on invariants that are guaranteed by non- unsafe code, so just putting the bound on the impl that contains the unsafe is not necessarily enough. unsafe代码是特殊的,因为它可以依赖非unsafe代码所保证的不变量,所以仅仅将边界放在包含unsafeimpl上是不够的。 But in all other cases, unless you really know what you're doing, you should avoid bounds on structs entirely.但在所有其他情况下,除非您真的知道自己在做什么,否则您应该完全避免对结构进行限制。

Trait bounds that apply to every instance of the struct should be applied to the struct:适用于结构的每个实例的特征边界应应用于结构:

struct IteratorThing<I>
where
    I: Iterator,
{
    a: I,
    b: Option<I::Item>,
}

Trait bounds that only apply to certain instances should only be applied to the impl block they pertain to:仅适用于某些实例的特征边界应仅应用于它们所属的impl块:

struct Pair<T> {
    a: T,
    b: T,
}

impl<T> Pair<T>
where
    T: std::ops::Add<T, Output = T>,
{
    fn sum(self) -> T {
        self.a + self.b
    }
}

impl<T> Pair<T>
where
    T: std::ops::Mul<T, Output = T>,
{
    fn product(self) -> T {
        self.a * self.b
    }
}

to conform to the DRY principle符合 DRY 原则

The redundancy will be removed by RFC 2089 : RFC 2089将删除冗余:

Eliminate the need for “redundant” bounds on functions and impls where those bounds can be inferred from the input types and other trait bounds.消除对函数和实现的“冗余”边界的需要,这些边界可以从输入类型和其他特征边界中推断出来。 For example, in this simple program, the impl would no longer require a bound, because it can be inferred from the Foo<T> type:例如,在这个简单的程序中,impl 不再需要绑定,因为它可以从Foo<T>类型中推断出来:

 struct Foo<T: Debug> { .. } impl<T: Debug> Foo<T> { // ^^^^^ this bound is redundant ... }

It really depends on what the type is for.这实际上取决于类型的用途。 If it is only intended to hold values which implement the trait, then yes, it should have the trait bound eg如果它只打算保存实现特征的值,那么是的,它应该具有特征绑定,例如

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
    }
}

In this example, only children are allowed in the school so we have the bound on the struct.在这个例子中,学校只允许孩子进入,所以我们对结构有限制。

If the struct is intended to hold any value but you want to offer extra behaviour when the trait is implemented, then no, the bound shouldn't be on the struct eg如果该结构旨在保存任何值,但您想在实现特征时提供额外的行为,那么不,边界不应该在结构上,例如

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
    }
}

In this example, not all customers are gold customers and it doesn't make sense to have the bound on the struct.在此示例中,并非所有客户都是黄金客户,并且对结构进行绑定是没有意义的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM