
Storing unboxed trait objects in Rust - donmcc
https://guiand.xyz/blog-posts/unboxed-trait-objects.html
======
cornstalks
This post reminds me of a problem I've hit with my Objective-Rust project[1]
(Rust code that's interoperable with Objective-C), to which I haven't found a
good solution.

The closest analog to Objective-C's @protocols in Rust are traits. I would
_love_ to be able to transliterate the following Objective-C:

    
    
      @protocol SomeProtocol
      // methods
      @end
    
      void demo() {
        // Just a demo of how to use SomeProtocol with a var:
        id<SomeProtocol> variable = nil;
      }
    

To the following Rust:

    
    
      #[objrs(protocol)]
      trait SomeProtocol {
        // methods
      }
    
      fn demo() {
        // Just a demo of how to use SomeProtocol with a var:
        let variable: *mut Id<SomeProtocol> = 0 as *mut _;
      }
    
      // The following is provided by objrs:
      struct Id<T: ?Sized>(core::marker::PhantomData<T>, Opaque);
      extern "C" {
        type Opaque;
      }
    

Unfortunately, RFC 255[2] breaks this. This fails because SomeProtocol is not
object safe. Trait objects in Rust can be pretty frustrating in Rust, as this
post illustrates.

I've had to workaround this in objrs by doing something like the following:

    
    
      #[objrs(protocol)]
      trait SomeProtocol {
        // methods
      }
      // The macro above generates stuff kinda like this:
      struct SomeProtocolId;
      impl SomeProtocol for SomeProtocolId {}
      impl<T: ?Sized + SomeProtocol> SomeProtocol for Id<T> {}
    
      fn demo() {
        // Now you have to know when to use SomeProtocol vs SomeProtocolId:
        let variable: *mut Id<SomeProtocolId> = 0 as *mut _;
      }
    

I'm going to see if there are some places where this blog post might simplify
some things in my code... it's got some interesting ideas.

[1]: [https://gitlab.com/objrs/objrs](https://gitlab.com/objrs/objrs)

[2]: [https://github.com/rust-
lang/rfcs/blob/master/text/0255-obje...](https://github.com/rust-
lang/rfcs/blob/master/text/0255-object-safety.md)

~~~
comex
Ideally you want `Id` to actually be parameterized on traits, not trait object
types that happen to have the same syntax (now deprecated) as their
corresponding traits. Of course, that's not currently possible. But you may be
interested in this repo for planning an RFC to eventually add such
functionality:

[https://github.com/Centril/rfc-trait-parametric-
polymorphism...](https://github.com/Centril/rfc-trait-parametric-
polymorphism/)

After being created 8 months ago, it's sort of inactive, but hopefully that'll
change once the compiler's feature-implementation backlog starts catching up –
in particular, once "chalk in rustc" becomes a reality.

~~~
cornstalks
Oh neat! I hadn't seen that one, but it's literally item #20 on my Rust
wishlist. I'll have to file an issue to give some motivating examples. Thank
you for pointing it out to me!

------
steveklabnik
The /r/rust thread is worth reading:
[https://www.reddit.com/r/rust/comments/a68ydk/storing_unboxe...](https://www.reddit.com/r/rust/comments/a68ydk/storing_unboxed_trait_objects_in_stable_rust/)

TL;DR this relies on unstable internal details; there’s a proposal to add an
API to let you do this kind of thing but it hasn’t been accepted yet.

------
shmerl
_> Rust, not being an object-oriented language, doesn't quite do inheritence
like the others. In C++ or Java, subclasses extend superclasses. In Rust,
structs implement traits._

I wouldn't say that Rust isn't object oriented. It just takes a different
approach to some things. Inheritance isn't really a strictly required feature
of the object oriented code design. Many for instance pointed out, that
composition is preferable:

[https://en.wikipedia.org/wiki/Composition_over_inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance)

~~~
millstone
Rust is clearly not object-oriented. It has very minimal support for late
binding, and no support for reflection or dynamic libraries.

This isn't a critique, it's just a statement of fact. No Rust designer will
say they set out to build a new object-oriented language.

~~~
shmerl
C++ has no reflection either. Are you saying it's not object oriented?

Rust has late binding (also known as dynamic dispatch). Check trait objects.
It's not really any worse than C++ virtual functions which is its form of
dynamic dispatch.

See [https://doc.rust-lang.org/book/ch17-02-trait-
objects.html](https://doc.rust-lang.org/book/ch17-02-trait-objects.html)

And Rust has dynamic linking as well: [https://doc.rust-
lang.org/reference/linkage.html](https://doc.rust-
lang.org/reference/linkage.html)

So IMHO Rust has enough features to be used in object oriented fashion.

------
v_lisivka
IMHO, Rust just should take size of the largest implementation of Animal for
`impl Animal`, and viola — we can make them static. No heap is good for
embedded.

~~~
MHordecki
Note that it's quite possible for a trait to have infinitely many
implementations in any given program. For example, Debug is implemented by
u64, Box<u64>, Box<Box<u64>>, ..., and so on. While these happen to have the
same size, it's not hard to imagine a situation where this is not the case.

~~~
majewsky
Your situation is already sufficiently impossible because it shows that for a
given trait, the set of implementors is often (countably) infinite.

~~~
comex
However, the number of implementors actually instantiated in any given program
is necessarily finite. You could write a program that would make it infinite,
e.g.

    
    
        trait Foo { fn foo(); }
    
        impl<T> Foo for T {
            fn foo() {
                <Box<T> as Foo>::foo();
            }
        }
    

...but the compiler can't compile it: since Rust implements generics solely
through monomorphization, that would require generating an infinitely large
binary :)

~~~
oconnor663
> Rust implements generics solely through monomorphization

That doesn't sound right. Trait objects use dynamic dispatch.

------
gerryxiao
Why boxed? just change to vec::<&dyn animal> and push reference of Dog to vec
,it should work. Isnot &dyn animal trait object?

~~~
albertgasset
It works, but the point of the article remains the same because &dyn Animal is
also a fat pointer.

------
luke6826522
Very insightful I enjoyed this very much

