Syntactic sugar for delegation of implementation

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.