Make certain methods of traits "non implementable"

So I have a struct that is impled like this :

pub trait Component: Send {
    fn new() -> Self where Self: Sized;

    fn init(&mut self, engine: &mut Engine) {}
    fn update(&mut self, engine: &mut Engine, delta_time: f64) {}
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
        self.update(engine, delta_time);
    }
}

This is going to be a part of a library and I want people to be able to impl that trait for themselves on their own structs. But I don't want them to be able to change the propagate_update method at all, so it would be public for my crate, but not for theirs. Is that possible in Rust ? Or should I change something ?

You can define propagate_update in a different trait and then use a blanket implementation:

trait Prop: Component {
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64);
}

impl<T> Prop for T
where
    T: Component,
{
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
        self.update(engine, delta_time);
    }
}
7 Likes

I think that's a good idea. Would this do about the same thing ?

pub(crate) trait Propagator {
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
        self.update(engine, delta_time);
    }
}

pub trait Component: Send + Propagator {
    fn new() -> Self where Self: Sized;

    fn init(&mut self, engine: &mut Engine) {}
    fn update(&mut self, engine: &mut Engine, delta_time: f64) {}
}

This will not compile as self.update doesn't exist in the Propagator trait, it needs to be a subtrait of Component to do so. The blanket implementation impl<T> Prop for T where T: Componentis also useful to automatically implement Propagator on types implementing Component

Oh yes, true thank you. When you say, it automatically is implemented on types that implement Component, can I still override it for some specific types (that propagate_update method needs to be different for a NodeComponent struct) ?

Not without enabling the specialization nightly feature. Unfortunately, the theoretical model hasn't been fully worked out yet, so you'd probably be stuck using the nightly compiler for quite a while.

If you want to have a trait that can only have implementations provided by your crate, you can use privacy annotations to seal it.

Because the whole point of this thing, is that I can implement whatever I want for this method, but other people can't.
This should be the method for the standard Component

fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
    self.update(engine, delta_time);
}

And this for NodeComponent Components

fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
    self.update(engine, delta_time);

    for i in self.children.iter_mut() {
        i.propagate_update(engine, delta_time);
    }
}

For now, I'll use specialization

You can get away without specialization, by using an unnameable type trick:

impl<T : Component> PropagateUpdate for T {
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64)
    {
        self.__propagate_update_impl__(engine, delta_time, Private)
    }
}

pub trait Component: Send + PropagateUpdate {
    fn new() -> Self where Self: Sized;

    fn init(&mut self, engine: &mut Engine) {}
    fn update(&mut self, engine: &mut Engine, delta_time: f64) {}

    #[cfg(feature = "docs")]
    /// **This method cannot be overriden.**
    fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64) {
        unreachable!("For docs only!");
    }

    #[doc(hidden)]
    fn __propagate_update_impl__ (
        self: &'_ mut Self,
        engine: &'_ mut Engine,
        delta_time: f64,
        _: Private, // <- this is what makes the whole function unimplementable for downstream users
    )
    {
        self.update(engine, delta_time);
    }
}

pub(in crate) use helpers::{Private, __ as PropagateUpdate};
mod helpers {
    pub struct Private;

    pub trait __ { // use this weird name to give it "less visibility" in the docs
        fn propagate_update(&mut self, engine: &mut Engine, delta_time: f64);
    }
}

Advantages of this approach:

  • by having Component be the subtrait rather than a super trait, people can call .propagate_update(...) simply by useing Component.

  • Some doc-related tweaks to help readability (real world example).

  • If you still want to provide overrides within your crate, the __propagate_update_impl__ escape hatch is available:

    impl Component for NodeComponent {
        ...
    
        fn __propagate_update_impl__ (
            self: &'_ mut Self,
            engine: &'_ mut Engine,
            delta_time: f64,
            _: Private,
        )
        {
            self.update(engine, delta_time);
            for child in &mut self.children {
                child.propagate_update(engine, delta_time);
            }
        }
    }
    
5 Likes

Neat trick ! Will definitely think about using it

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.