Storing unboxed trait objects in Rust
guiand.xyz
guiand.xyz
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
[2]: https://github.com/rust-lang/rfcs/blob/master/text/0255-obje...
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.
objrs already supports optional methods (for externally-defined protocols), though using them is the same as in Objective-C (you still have to call respondsToSelector: first). I've thought about adding an additional API that simplifies this a bit (i.e., a method that returns an Option, where it's None if the class doesn't respond to the method, or Some(fn) if it does (where fn is a closure you can use to invoke the function)).
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.
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:
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.
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
And Rust has dynamic linking as well: https://doc.rust-lang.org/reference/linkage.html
So IMHO Rust has enough features to be used in object oriented fashion.
Rust does have some support for reflection, though it is quite limited. https://doc.rust-lang.org/std/any
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 :)That doesn't sound right. Trait objects use dynamic dispatch.