Is there a way to make a variable behave as though it only implements one trait?

Let's say I have a type T that implements 2 traits A and B.

I can instantiate the type with let t = T{};, sure, but then it can call methods from both traits later, and I don't want that. I want t to behave like it only implements A and not B. In other places, with other instances of T, I'll have the reverse restriction.

My first thought is to use trait objects. But that comes with dynamic dispatch, and boxed trait objects not implementing the trait by default, so I can't just pass them to functions like fn f(x: impl A) with no extra work or cost. Given that I know the actual type is T, and this is clear where the variable is created (like let t: Box<dyn A> = Box::new(T{});), is there really a need to bring dynamic dispatch into this at all? I don't even need the data to be on the heap instead of the stack.

Another option I'm seeing is newtypes.
struct Ta(T);
impl A for Ta {}
But then I have to do delegation for every single method of A, and I have to do that for every trait I'm dealing with (there are more than just the two).

Or have a function like this for every trait:
fn as_a(&self) -> impl A where Self: Sized + Copy {*self}
(This approach is winning on convenience so far)

I suppose what I want to be able to do this:
let t: impl A = T{} as A;
As a shortcut to get whatever is returned by the impl A return type.

Is there some sort of notation or wrapper type that does exactly what I want?

Playground: Rust Playground

1 Like

What are the actual traits A and B in your case?

If I needed what you're asking for I'd probably use

fn impl_a<U: A>(U: u) -> impl A { u }

but maybe the method is more convenient for you.

Unfortunately I don't think I have anything to add that helps you, at least, not without knowing more. Some not-so-helpful comments follow...


Still not what you want, but &dyn Trait A would keep it on the stack. You'd still need + ?Sized on fa. (But it makes sense there anyway so far as your example shows.)

Something... sort of like that is planned eventually. But it's less ergonomic than what you have, not more.

    type TA = impl A;
    let ta: TA = T {};
    ta.a();
    fa(&ta);
    // fails
    // ta.b();

as impl A is an interesting thought.

3 Likes

Would something like this meet your needs?

#[repr(transparent)]
struct AsA<T>(T val);

impl <T> A for AsA<T> where T: A { ...pass through methods... }

You could add implementations of Into or AsRef to make constructing and working with trait-restricting newtypes a bit more ergonomic, if you like.

I don't have a solution, but I'm wondering how you could tell that it does not implement B, or what is the negative impact of implementing B? Are you just trying not to make a mistake and accidentally call a method that you shouldn't be calling?

One thought, if A xor B is used on a per module basis, is to only use A; or use B; in the modules that it should be used in, since traits have to be in scope to be called. But you could still make a mistake if your IDE auto-imports the trait when you try to call it.

1 Like

Okay, I've nearly got it.

A method returning impl $the_trait for every trait. Generated by macro for convenience.

Then I can just do let t = A::as_trait(T{}); to get what I want.

I can live with one-liner macros in every trait definition, but is there a way to get the trait identity automatically in the macro? I'd rather the macro invocations to simply be fn_as_trait!().

Code:

macro_rules! fn_as_trait {
    ($the_trait:ident) => {
        fn as_trait(self) -> impl $the_trait where Self: Sized {self}
    }
}

trait A {
    fn a(&self) {}
    fn_as_trait!(A);
}

trait B {
    fn b(&self) {}
    fn_as_trait!(B);
}

fn fa<U: A>(_x: &U) {}
fn fb<U: B>(_x: &U) {}

struct T {}

impl A for T {}
impl B for T {}

fn main() {
    let t = T{};
    t.a();
    t.b();
    fa(&t);
    fb(&t);
    
    let ta = A::as_trait(T{});
    ta.a();
    // ta.b(); // Error (good)
    fa(&ta);
    // fb(&ta); // Error (good)
    
    let tb = B::as_trait(T{});
    // tb.a(); // Error (good)
    tb.b(); 
    // fa(&tb); // Error (good)
    fb(&tb); 
}

When it comes to foreign traits, this is the closest approach I've found to being kind of easy to use.

Would be much easier if closures could return impl $the_trait though.

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.