3
votes

Consider some struct (HiddenInaccessibleStruct) that is not accessible, but implements an API trait. The only way to obtain an object of this hidden type is by calling a function, that returns an opaque implementation of this type. Another struct owns some type, that makes use of this API trait. Right now, it seems not possible to assign this field in fn new(). The code below can also be found in rust playgrounds.

// -- public api 
trait Bound {
    fn call(&self) -> Self;
}

// this is not visible

#[derive(Default)]
struct HiddenInaccessibleStruct;

impl Bound for HiddenInaccessibleStruct {
    fn call(&self) -> Self { }
}

// -- public api
pub fn load() -> impl Bound {
    HiddenInaccessibleStruct::default()
}

struct Abc<T> where T : Bound {
    field : T
}

impl<T> Abc<T> where T : Bound {
    
    pub fn new() -> Self {
        let field = load();
        
        Abc {
            field // this won't work, since `field` has an opaque type.
        }
    }    
}

Update The API trait Bound declares a function, that returns Self, hence it is not Sized.

1
You may need box - prehistoricpenguin

1 Answers

3
votes

There are two concepts in mid-air collision here: Universal types and existential types. An Abc<T> is a universal type and we, including Abc, can refer to whatever T actually is as T (simple as that). impl Trait-types are Rust's closest approach to existential types, where we only promise that such a type exists, but we can't refer to it (there is no T which holds the solution). This also means your constructor can't actually create a Abc<T>, because it can't decide what T is. Also see this article.

One solution is to kick the problem upstairs: Change the constructor to take a T from the outside, and pass the value into it:

impl<T> Abc<T>
where
    T: Bound,
{
    pub fn new(field: T) -> Self {
        Abc { field }
    }
}

fn main() {
    let field = load();
    let abc = Abc::new(field);
}

See this playground.

This works, but it only shifts the problem: The type of abc in main() is Abc<impl Bound>, which is (currently) impossible to write down. If you change the line to let abc: () = ..., the compiler will complain that you are trying to assign Abc<impl Bound> to (). If you try to comply with the advice and change the line to let abc: Abc<impl Bound> = ..., the compiler will complain that this type is invalid. So you have to leave the type of abc being implied. This brings some useability issues with Abc<impl Bound>, because you can't easily put values of that type into other structs etc.; basically, the existential type "infects" the outer type containing it.

impl Trait-types are mostly useful for immediate consumption, e.g. impl Iterator<Item=...>. In your case, with the aim apparently being to hide the type, you may get away with sealing Bound. In a more general case, it may be better to use dynamic dispatch (Box<dyn Bound>).