Is it possible to add new methods to a trait from an external crate?

I've tested whether Rust allows adding new methods (aka. extension methods) to a trait from an external crate (in this test, strfmt):

use strfmt::Format;

impl dyn Format {
    fn x(&self) -> i64 {
        0
    }
}

fn main() {
    println!("Hello, world!");
}

I get:

error[E0116]: cannot define inherent `impl` for a type outside of the crate where the type is defined
 --> src\main.rs:3:1
  |
3 | / impl dyn Format {
4 | |     fn x(&self) -> i64 {
5 | |         0
6 | |     }
7 | | }
  | |_^ impl for type defined outside of crate.

It'd be nice if this were possible because I'm planning to build a component set that may be extended by the library user. For example, I could've a DisplayObject and a subtype TabBar which defines methods that don't exist in DisplayObject. Is there a proposal for allowing this?

impl dyn Trait is not a useful syntax. It doesn't do anything to the trait. It's a misleading syntax that creates inherent (non-trait) implementation for a concrete type that is created when the trait is used with dynamic dispatch. Even if this code compiled, it wouldn't work with the trait used in generic contexts.

It's not possible to add new methods to an existing trait.

It's possible to create a new trait that has its own new methods, and requires the old trait to be implemented too:

trait MyFormat: strfmt::Format {
    fn woof() {}
}

(in std Ord and PartialOrd are related like that).

In some cases you can also create a blanket implementation based on the old trait:

impl<T> MyFormat for T where T: strfmt::Format {
}

Note this is not impl Trait for Trait — that is not supported. It's impl Trait for Type that implements Trait. In std From and Into are like that.

6 Likes

I've not thought of doing this, but trait inheritance is problematic if I've something like Rc<DisplayObject> and Rc<Control>. Suppose DisplayObject is a super type of Control (sub trait). Then I've trait Control: DisplayObject. Then, suppose DisplayObject uses Rc<DisplayObject>, how to use it as Rc<Control>?

Anyway, I see... so the impl dyn T doesn't really work.

An example of the "Extension Trait" pattern @kornel mentioned is the byteorder::WriteBytesExt trait. This adds methods like write_u32() and write_f64() to any type that implements the std::io::Write trait, letting you write a number to the underlying stream using the correct endianness.

Trait inheritance is probably not what you want here, at least in the way you've said that trait Control: DisplayObject. Instead of thinking of it as "Rust's version of inheritance" it's more like "any type that implements Foo also needs to implement Bar".

1 Like

I've not tested yet, but that impl<T> Control for T where T: DisplayObject {} approach (where Control declares a method x()) ensures I can do display_object_reference.x() (where display_object_reference: Rc<DisplayObject>)? Is that right? If so, this is what I want.

I've just tested in the playground, no success...: Rust Playground

Anyway, I don't want this approach because I'll need to override all Control member methods based on what structures implement Control.

Rust doesn't have subclassing/dynamism like that. What you're trying to do seems like for an OO scripting language with an inheritance hierarchy or monkey patching. This approach is doesn't fit Rust's "flat" traits and methods known at compile time.

You could use Box<dyn Any> and downcast to Control if you want to use Control's methods on arbitrary objects. There's no way to use Control's methods when you have an object that is explicitly a DisplayObject trait instance unless you modify the DisplayObject trait to have as_control() or as_any() method that gives you a way to cast it to a Control instance. From Rust's perspective if it's DisplayObject it's this and only this, nothing else.

Traits have a different philosophy. Instead of using them in OOP way to describe what an object is (it's a control, and controls can do "X") you use them to describe what an object can do (it can do "X", because there's impl TraitX for Widget1).

2 Likes

Theres rc.downcast() for my purpose in this sense, but what about rc.upcast()? There's no such thing...

If you have a concrete Control instance and there's trait Control: DisplayObject then you can use functions like foo<T: DisplayObject>(arg: &T) and the concrete (non-dyn) type that implements Control will just work with it, because it implements both traits.

Rc<T> can dereference to &T at zero cost, so Rc itself doesn't need to implement any traits. You can use foo<T: DisplayObject>(arg: Rc<T>) too if refcounting is essential for the method.

If you have to work with Rc<dyn Control>, you will need to make Control trait have explicit fn as_displayobject() -> &dyn DisplayObject, because Rust doesn't magically implement this yet. Rust isn't a true OO language, so OO patterns are clunky like that. trait A: B syntax isn't inheritance, but a short version of trait A where Self: B.

If you have Rc<dyn DisplayObject> or Rc<T> where T: DisplayObject, then I'm afraid there's no way to discover if it's actually a Control without modifying DisplayObject to have methods like as_any() or as_control.

2 Likes

Thank you for the explanation! I'm yet stuck because in practice I can't call downcast with a trait object as callee base: Rust Playground

fn main() {
    let o: Rc<dyn Any> = Rc::new(TabBar);
    println!("{}", o.downcast::<dyn Control>().unwrap().x()); // 64
}

And r.downcast really takes self.

This program is just missing a ?Sized implicit bound removal to make it work with trait objects:

impl<T: ?Sized> Control for T where T: DisplayObject {
}

With that change it compiles.

I'm not using that generic impl anymore in the test program. How can I make a ?Sized bound when implementing for a specific type?

impl DisplayObject for TabBar where TabBar: ?Sized {
}

impl Control for TabBar where TabBar: ?Sized {
    fn x(self: Rc<Self>) -> i64 {
        64
    }
}

This post doesn't address your question or situation, but may clarify the language some.

Not for what you want, as it doesn't do what you thought. But it does do something.

dyn Trait is its own, single, concrete type. It performs dynamic dispatch via a vtable and it is dynamically sized, and you can type erase multiple types to it, but it is still a statically known type on its own. It is used primarily for the type erasure aspect, where you need to store many base types as a singular type with some capabilities, or name an otherwise unnameable type, etc. Due to its dynamic size, you usually see it in a Box, behind a &, or behind some other type of pointer.

Implementing for dyn Trait (either inherently or another trait) implements for that dyn Trait type. Implementing for dyn Trait does not implement for all types that implement Trait and does not modify the trait Trait either. It is also a rather niche thing to do.

The dyn Trait type "belongs to" whomever the trait Trait belongs to, in terms of who can implement what for it. So you can't write an inherent impl for a foreign dyn Trait, or implement a foreign trait for one, but you could implement your own trait on a foreign dyn Trait.

The dyn Trait types do have the limitation that inherent impls don't take precedence over trait methods as per normal, but that's an inconsistency I personally hope is some day removed.

1 Like

You don't need to. ?Sized is something to remember when writing blanket impls, especially for extension traits, and that's the context in which I mentioned it. For impls on concrete types, it is not relevant.

1 Like

I keep getting:

error: the `downcast` method cannot be invoked on a trait object
    --> src/main.rs:26:22
     |
26   |     println!("{}", o.downcast::<dyn Control>().unwrap().x()); // 64
     |                      ^^^^^^^^

While the docs say downcast takes self, it works as Rc::downcast<T>(this) instead of o.downcast::<T>(), which is strange. However... I'm unable to downcast to dyn Control because it's dynamically sized. Is there any proposal that makes it possible?

When downcasting in Rust, you (attempt to) downcast to the erased type. In that snippet, you would only be able to successfully downcast to a TabBar.

You can switch between dyn Trait types like so. But I'm not sure this actually accomplishes anything you're trying to do.


As for why Rc::downcast is called that way, it's due to its smart-pointer nature:

The inherent methods of Rc are all associated functions, which means that you have to call them as e.g., Rc::get_mut(&mut value) instead of value.get_mut(). This avoids conflicts with methods of the inner type T.

1 Like

Actually, not exactly zero-cost, because it needs to find the address of the aligned field. But surely cheap enough.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.