Syntactic sugar for delegation of implementation

I would find this very useful. D has a similar feature fwiw. It is one of these things that are not necessary but super nice to have and makes some patterns pleasant to write.

I really like this idea, my only worry is that I could accidentally implement a method when I didn’t plan to.

For example, I start with this initial definition:

trait Foo {
  fn do_a();
  fn do_b();
}

struct X {}

impl Foo for X {
  fn do_a() {
    ...
  }

  fn do_b() {
    ...
  }
}

struct Y {
  x: X,
}

impl Foo for Y use self.x;

If I then redefine Foo and X like so:

trait Foo {
  fn do_a();
  fn do_b();
  fn do_c();
}
...
impl Foo for X {
  fn do_a() {...}
  fn do_b() {...}
  fn do_c() {...}
}

Then Y will implicitly get the do_c behavior. I would propose the syntactic sugar be a little more explicit, line so:

impl Foo for Y use self.x for do_a, do_b;

This way, when do_c is defined on Foo and X, the compiler will fail on Y's implementation of Foo.

It still saves on writing lots of boiler plate, but you get the advantage of being explicit.

Disagree. Inheritance is a misfeature.

1 Like

Argh. I tried to do this. I'm going to just post my whole question as it has a pretty good compelling example:


Let's say that I'm prototyping a new struct, and I can emulate it with a Vec to get off the ground. I'd also like to expose an iterator. That's great, as I can just reuse the iter method from the slice:

struct SparseArray(Vec<u8>);

impl SparseArray {
    fn iter(&self) -> ??? {
       self.0.iter()
    }
}

A problem arises when I go to define the result type; I have to expose that I'm using a slice iterator! I plan on implementing this using something more suitable, so I don't want consumers to actually know that there's a slice under the hood. Maybe my internal representation will change a few times, so I want to hide the implementation details, like a good programmer.

The best solution I am aware of is to create a newtype around slice::Iter, but that presents a different challenge: now we have to define the Iterator trait for the newtype:

struct SparseArrayIter<'a>(slice::Iter<'a, u8>);

impl<'a> Iterator for SparseArrayIter<'a> {
    type Item = &'a u8;

    fn next(&mut self) -> Option<&'a u8> { self.0.next() }
}

Yay! I've wrapped my iterator.

But I should probably wrap size_hint... oh, and it turns out that nth has a specialized implementation so I should call it too. And what about the other ~50 iterator methods?

A proposal

I'd like to be able to write something like this:

#[delegate(Iterator, to = "self.0")]
struct SparseArrayIter<'a>(slice::Iter<'a, u8>);

And the ever-helpful compiler can just write out all of those annoying shim methods. I started a syntax extension to do this, but immediately ran into a problem: there's no way to get information about other types.

So, what alternatives do we have? I think that an RFC is needed, in order to have a hook with the appropriate data available.

Note that I've used a trait here, but I think that conceptually it should work with delegating 1 or more methods to any field of a struct.

Inheritance for the sake of code reuse is a misuse of inheritance, certainly. However, inheritance does have a place, if you follow Liskov. However, you can't really argue that by misusing inheritance, you do get code reuse very easily. This topic is all about having an equally-easy way to get reuse without having inheritance.

As does Haskell, as "generalized newtype deriving, if I understand correctly.

1 Like

I understand the concern but I think implicit implementations of new methods can also be considered as a desirable feature. A solution could be to allow both:

// Please implement everything. I'm an adult. I understand the risks.
impl Foo for Y use self.x for ..;

If a plugin is undoable now, is it really worth it waiting for a hypothetical improvement of syntax extension rather that just adding this feature as a syntactic sugar?

I think we agree. Maybe my use of the word inheritance introduced misunderstanding with some participants. What I call inheritance (possibly incorrectly, tell me) encompasses both subtyping and a certain form of code reuse. Now I think we are several to agree that precisely it is a flaw of many OO languages that they entangle subtyping and code reuse whereas they should exist as two orthogonal concepts.

Is there actually a time that you need to use the parent as a T: Flying + Illuminating? If not you could just pass a references to the individual member when you want to call a function that needs one of those traits.

Alternatively, do Flying and Illuminating actually need to be completely separate traits like that? If you find yourself needing both a lot then maybe it’s not worth separating them.

@DanielKeep has made some interesting and useful experiments in this direction:

https://danielkeep.github.io/rust-custom-derive/doc/newtype_derive/index.html

It’s a lot of macro magic for it to work in a limited way, an RFC for language support seems warranted, it would be a big ergonomic win for composition over inheritance.

1 Like

In simple cases you can use dereference and macro (i don't think this is a good style, but works):

macro_rules! emerge {
    ($from:ty => $to:ty, $by:ident) => {
    	impl Deref for $from {
    	    type Target = $to;

    	    fn deref<'a>(&'a self) -> &'a $to {
    	        &self.$by
    	    }
    	}
    };
}

struct Firefly {
    luciferin: Luciferin,
    insect: Insect
}
emerge!(Firefly => Luciferin, luciferin);


fn main() {
	let ff = Firefly { luciferin: Luciferin, insect: Insect};
	ff.illuminate();
}

The main problem is you can implement Deref this way once only.

What about to add sugar like dereferencing but which can be implemented many times (like From) and uses to level up methods (struct field references returns)?

Unless I’m hugely misreading it… You want something like multiple automatic derefs to be possible at once? But more fine-grained as to which traits apply to which member in the type you’re extending.

Dlang has something similar; although it’s tied to the type definition time. The implementation might be of interest:

  • opDispatch(string op: "method_name")() (for forwarding specific method calls; although with some meta-programming you could automate all the decisions for which call to forward where);
  • and alias <member> this ((an incomplete) way to provide subtyping in value types via composition).
1 Like

This is just a toy example to illustrate the concept. In a real example I may need to delegate implementations of an unlimited number of independent traits to an equal number of pre-existing types.

No I don't. Dereferencing (even multiple derefs) can work in very simple cases but it does not allow what I expect. Consider a developpement of DenisKolodin's example:

fn cumulate_light<T: Illuminate>(lightProcuders: &[T]) {
    println!("glowing all together");
}

fn main() {
	let ff1 = Firefly { luciferin: Luciferin, insect: Insect};
	let ff2 = Firefly { luciferin: Luciferin, insect: Insect};
	let lightProcuders = [ff1, ff2];
	// fails to compile !!
	cumulate_light(&lightProcuders);
}

"Dereferenciable" types do not implement any new trait (except trivially Deref). Sure Firefly seems to benefit from Luciferin's implementation of Illuminate but this is only a superficial behaviour. Firefly is still not a kind of Illuminate and then any call to cumulate_light fails.

[quote=“burakumin, post:22, topic:2633”]This is just a toy example to illustrate the concept. In a real example I may need to delegate implementations of an unlimited number of independent traits to an equal number of pre-existing types.[/quote]Sure. My question was about how independent the traits are. How often do you need to write code that depends on more than one trait at once?

A classic example of handling this kind of thing without inheritance is entity-component systems, often used in game engines. Entities are built out of components by composition, rather than inheritance, but rather than delegating everything through the entity to its components, code just works directly with the components themselves.

So you want the extended types to be recognized as implementing these traits themselves?

So, effectively, sub-typing but without down-casting. Kind of (I’m assuming that’d hold only for generically-bound slots (function parameters or whatever)).

That’s about half way between opDispatch and alias-this. But really, the best word for it is ‘delegating’ the implementation, as you put it. Does any language actually have this (I feel it’s quite a specific fit for Rust’s combination of features; not a bad thing, really)?

I've got fun with this tricky problem :grinning: Look at this, more abstract and powerful example (with different interfaces provide). Maybe it also be useful Rust Playground


trait Flying {
    fn fly(&self);
}

struct Insect;
impl Flying for Insect {
    fn fly(&self) { println!("Insect") }
}

struct Turbine;
impl Flying for Turbine {
    fn fly(&self) { println!("Turbine") }
}

trait Illuminating {
    fn illuminate(&self);
}

struct Luciferin;
impl Illuminating for Luciferin {
    fn illuminate(&self) { println!("Luciferin") }
}

struct Lamp;
impl Illuminating for Lamp {
    fn illuminate(&self) { println!("Lamp") }
}

macro_rules! emerge_by_ref {
    ($from:ty => $to:ty, $by:ident) => {
        impl AsRef<Box<$to>> for $from {
            fn as_ref(&self) -> &Box<$to> {
                &self.$by
            }
        }
    };
}

struct FlyGlow {
    fly_as: Box<Flying>,
    glow_by: Box<Illuminating>,
}
emerge_by_ref!(FlyGlow => Flying, fly_as);
emerge_by_ref!(FlyGlow => Illuminating, glow_by);

impl FlyGlow {
    fn new<F: Flying + 'static, I: Illuminating + 'static>(fly_as: F, glow_by: I) -> Self {
        FlyGlow {fly_as: Box::new(fly_as), glow_by: Box::new(glow_by)}
    }
}

fn cumulate_light<T: AsRef<Box<Illuminating>>>(lightProcuders: &[T]) {
    println!("glowing all together");
}
fn cumulate_fly<T: AsRef<Box<Flying>>>(flyers: &[T]) {
    println!("fly all together");
}

fn main() {
    let firefly = FlyGlow::new(Insect, Luciferin);
    let plane = FlyGlow::new(Turbine, Lamp);
    let somethingProcuders = [firefly, plane];
    cumulate_light(&somethingProcuders);
    cumulate_fly(&somethingProcuders);
}

I do not think this has necessarily to do with code that depends on more than one trait. But yes I can imagine simple examples where multiple traits are required:

fn to_fast_map(m: BTreeMap<K, V>) -> HashMap<K, V> where K: Hash + Eq + Ord { ... }
fn to_ordered_map(m: HashMap<K, V>) -> BTreeMap<K, V> where K: Hash + Eq + Ord { ... }

I understand the use case but except maybe in specific examples I tend to consider this a bad design. You are exposing the content of your entities. This is against most of principles of limiting visibility of internals and of separating interface and implementation. In particular subcomponents may expose behaviors you do not want the external world to access. What if some invariants exist over multiple components? If it were me I would cope with the burden of writing explicit delegating methods rather than exposing all the internal state. But I can understand sometime you want to be lazy. I think my proposition allow you to be lazy without sacrificing visibility control.

Additionally I think you should not focus too much on composition as a requirement of this proposition. I'm not only considering object that can naturally be thought as aggregations of sub-items. I'm thinking the other way round: what if for implementing a specific trait A for my type T, I could isolate a sub-state substate: S that is dedicated to handling this trait internally? If I could, instances of S would be used as internal components of any type that needs the same implementation for A. So here composition is a possible consequence of this design. And now if you go further you realize you do not necessarily have to consider something as specific as a sub-state (a struct field). All you need is a internal transformation (a function) of type T -> S. Extracting a field |t: T| t.substate is just a peculiar case.

Not that I'm aware of. But I'd like many other language to better handle code reuse (without mixing it with type inheritance).

I'm not sure what you try to show here. That you can trick the type system by relaxing some conditions?

I agree that some examples not suit you want. But I'm thinking language has a great design, because it consists of safe approaches, and it has powerfull possibilities to transform abstractions into solid native code with low runtime cost. Most of metas in Rust is explicit. It's true. But there are some ideas:

  1. For shortening code you can use macros, this is a common way in system languages.
  2. If your software needs large amount of pluggable abstractions than good idea to embed meta-language like Lua.

I don't think the large project with hard inheritance will be simple to maintain and debug.

On the other hand, let's look at embedded GC. I think that's more abstract and simply approach to work with. But absence of GC is the reason for which we love Rust :heartbeat:

I think several people have expressed their interest in this proposition. Sorry if the answer to my question is obvious but what would be the next step if I wanted this feature to be included in future versions of Rust? Write a formal RFC? Make a POC by forking the compiler? Is there some standard procedure in that case?

I’d like to correct one inaccuracy in terminology from the discussion above:

inheritance IS a form of code reuse. In fact that is the original purpose of inheritance. Smalltalk is the canonical example of this (a dynamic language!)

When people talk about the Liskov principle they actually talk about a sub-typing relation, not inheritance.

The canonical way to make a proposal for something to be included in Rust is to write an RFC. This typically gets input from many community members and will get a formal decision deciding whether the specific feature is to be implemented or not.

These are sometimes preceded by a Pre-RFC or even a Pre-Pre-RFC which are typically posted on internals.rust-lang.org but sometimes just linked from internals to a proposal stored in someones fork of RFCs. A serious proposal follows this form and is typically quite thorough and detailed though it isn’t quite unheard of for some to just waive things they don’t know like implementation details by saying “I’m not capable of implementing this” or “I don’t know how it would be implemented.”

If you’d seriously like to try to push this forward, you might want to try writing a Pre-RFC in the standard format and post it. People are more likely to invest time in a serious proposal which effectively tries to motivate and detail your desired feature than a simple inquiry about whether it is feasible and useful.

An easier route would be to file an RFC issue which effectively stores it away until someone decides it’s worth an RFC someday in the future. Then it would actually be under consideration. An RFC gets much more attention than an issue.

RFC created:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.