Skip to content

Instantly share code, notes, and snippets.

@matthewjberger
Forked from lifthrasiir/no-box.md
Created January 3, 2017 18:35
Show Gist options
  • Save matthewjberger/203901df51067443da4a69fbf8e5c80d to your computer and use it in GitHub Desktop.
Save matthewjberger/203901df51067443da4a69fbf8e5c80d to your computer and use it in GitHub Desktop.
Idiomatic Rust: Yes I'm really trying to write something similar

No Box<T>

tl;dr: Avoid Box<T> in general.

Actually, this rule is so important that the Rust Pointer Guide explicitly says the same. Therefore without a further ado, you should avoid Box<T> except for these three cases:

  1. When you absolutely need a trait object (Box<Trait>). But review carefully to see if it is indeed absolutely needed; you may try to generalize things too far, for example.

  2. When you have a recursive data structure. This may be mandatory when you have an inherently recursive data (e.g. scene graph), but it may also be a sign of the premature optimization. Again, review carefully to see if you need to write a separate data structure yourself, and use the collection crate if possible.

  3. When it is beneficial for the data layout. For example, this enum is 1032 bytes long in my machine:

    enum A {
        A1(uint),
        A2([uint, ..128]),
    }

    ...while this enum is only 16 bytes long:

    enum B {
        B1(uint),
        B2(Box<[uint, ..128]>),
    }

    This is quite helpful for large enums. But keep it mind that this case is very unusual, so do not try to micro-optimize every enum with Box<T>.

Also, no matter the above exceptions say, your function should not receive or return a non-trait T as Box<T>. Unlike many other pointer-like types, one can freely unwrap T out of Box<T> and vice versa. There is no performance penalty on moving a large struct into/from the function, so let the caller decide.

No Useless Trait

tl;dr: Do not make a trait when it is likely to be implemented by a few, known types in the same crate.

Traits are indeed a fine and generalized way to provide the polymorphism in Rust. They subsume many interconnected concepts like classes, interfaces, inheritance and virtuality under one unified feature. Still, it is only useful when you need the polymorphism. I've seen something like this many times:

struct A { ... }
trait AFields { ... }
impl AFields for A { ... }
// and nothing else

Seriously, why do we need a trait when it will ever be implemented by only one type? Having a separate trait actually hampers its use since one has to explicitly import the trait to use that (though there is an RFC for removing this restriction).

One may argue that it is for the future extensibility. I'd say, not really. If you do care about the extensibility you can provide accessor methods in the traitless impls. The additional types can then implement a new trait with the same set of accessor methods; the original struct will continue to provide its own methods without any new imports. But more importantly, such extensibility should be carefully planned ahead of time with a good justification. Having one concrete type for one trait hardly justifies more complex design.

While this is not a hard and fast rule, this equally applies in general: two or three types barely make a difference compared to one type, and enums should be used instead. It is also possible that the trait can be implemented by multiple unknown types (but only one known type), but then you may not fully understand the interface required by unknown types. Try to implement such unknown types first, and make sure that the design is justified even after that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment