`bare_trait_objects` and constructors

I have an factory-method constructor like this:

impl dyn Transport {
    pub fn new(s: &str) -> Result<Box<dyn Transport>> {
        // Imagine in the future this returns various impls depending on `s`
        Ok(Box::new(local::LocalTransport::new(&Path::new(s))))
    }
}

With rustc 1.53.0-nightly (07e0e2ec2 2021-03-24) I now get a warning

    |
248 |         let transport = Transport::new(&temp.path().to_string_lossy()).unwrap();
    |                         ^^^^^^^^^ help: use `dyn`: `<dyn Transport>`

warning: trait objects without an explicit `dyn` are deprecated

I can understand how to change it to <dyn Transport>::new(...) - in fact rust-analyzer suggests the change for me, which is awesome. But this makes the calling code rather ugly.

I can also understand, I think, why in general Rust is moving towards requiring an explicit dyn on use of trait objects.

My questions are:

  1. Why is a dyn recommended here? As far as I can see the call to Transport::new is not going through a vtable or fat pointer or in any sense dynamic.

  2. Is this a signal that this is not a natural rustic way to write an abstract constructor? What would be better? (The obvious step would be to move it to an unassociated function.)

The long-term goal is to eventually forbid leaving out the dyn entirely when referring to trait objects (through an edition). The call isn’t going through a vtable but it’s still calling an associated function of the type dyn Transport, not a method of the trait Transport. The difference is: dyn Transport is the type of trait objects for the trait Transport; it’s an unsized type that implements the trait Transport. On the other hand Transport (without the dyn) is currently ambiguous (either the trait or the trait object) but intended to, in the future, unambiguously refer to just the trait.

If you have a trait Foo then paths with the Foo prefix, e.g. Foo::bar can only refer to trait items, i.e. methods, associated types, or associated constants.

In my (personal) opinion, the call <dyn Transport>::new() does look kind-of weird indeed. If you want to use a “Transport”-related prefix, you could make it a standalone function in a module called transport. This would be similar to how the standard library trait Iterator comes with freestanding functions in the iter module such as iter::once, iter::empty, iter::repeat, or iter::from_fn. So you could have a transport::new, or even give a more descriptive name. (Functions called new are usually used for types, in particular structs.)

There’s also the precedent of other types using non-path syntax. The standard library seems to avoid the need for calling <...>::foo-style functions by avoiding associated functions (without a self parameter) on such types. Take something like a slice for example. The standard library does not have a <[T]>::from_mut or a <&mut [T]>::from_mut function, but instead a slice::from_mut function in a module called slice.

3 Likes