How to hide a struct used as a generic trait parameter?

I'm implementing a function which is generic over implementations of nearest-neighbor maps. I'd like to hide the exact details of how I use this nearest-neighbor map, but require that maps still implement a generic trait when one of the generic parameters is a "secret" struct - in other words, they can only use a blanket implementation to implement the helper trait.

I've included a stripped-down version of what I'd like to do here:

mod lib {
    pub trait Helper<T> { }
    
    struct Secret;
    pub struct Zeb<B>(B);
    
    pub fn foo<B: Helper<Secret>>(b: B) -> Zeb<B>  { 
        Zeb(b) 
    }
}

mod client {
    use super::lib::{foo, Helper};
    
    struct Bar<T>(Option<T>);
    
    impl<T> Helper<T> for Bar<T> {}
    
    pub fn baz() {
        // error: type `Secret` is private
        let zeb = foo(Bar(None));
    }
}

Is there a way that I can expose the function foo without exposing Secret as a public type, while still letting users call foo with a custom helper?

You already have, but to silence the private_bounds lint, you may want to use the canonical sealing pattern.[1]

    mod private {
        pub struct Secret;
    }

    pub fn foo<B: Helper<private::Secret>>(b: B) -> Zeb<B>  { 
        Zeb(b) 
    }

I'm not seeing that error. Perhaps you simplified too much?


  1. Much more common for traits, but also works for types. ↩ī¸Ž

2 Likes

What's weird for me is that (ignoring visibility errors for a second), you're trying to pass a Bar<?> (implementing Helper<Option<?>>) to a function taking a Helper<Secret>.

This wouldn't work, even if the Secret type was visible to the caller.

Did you maybe mean to blanket implement Helper<Secret> for Bar<T> regardless of its generic argument T (so Bar<?> would implement Helper<Secret>)?

1 Like

Is there something wonky happening in transcription? I've copied over to a playground here which reproduces the error for me.

Your recommendation to use the canonical sealing pattern does in fact work, though, which I think is the real solution.

#![allow(dead_code, private_bounds)]

mod lib {
    pub trait Helper<T> {}

    mod private {
        pub struct Secret {}
    }
    use private::Secret;
    pub struct Zeb<B>(B);

    pub fn foo<B: Helper<Secret>>(b: B) -> Zeb<B> {
        Zeb(b)
    }
}

mod client {
    use super::lib::{foo, Helper};

    struct Bar<T>(Option<T>);

    impl<T> Helper<T> for Bar<T> {}

    pub fn baz() {
        // error: type `Secret` is private
        let _zeb = foo(Bar(None));
    }
}

No - rather, I want to force the user to implement Helper<Secret> for Bar<Secret>, without allowing them to reference the Secret. If you'd like an example which more closely models what I'm trying to achieve (but has the same error as the simplified version), check here. However, this is all just noise on top of the original issue in the OP.

1 Like

Weird. #![allow(dead_code)] is what triggers the error; remove that and it's just a warning. That feels like a bug to me.

(Putting pub on the second mod also results in an error, but that makes more sense to me.)

It's not. You'll still get the error, just more warnings.

2 Likes

Just make Secret pub(crate) then. It'll fix the error.
The user will not be able to use it from outside the crate.

Ah whoops, yeah I just missed something entirely then. Sorry for the noise.

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.