Limits of type inference smartness


#1

Hi. I’m trying to reduce the number of symbols visible from our object files during linkage by reducing the number of reachable trait methods (currently they are all marked as reachable). So, I need to find out what methods can be called from other crates. However, type inference stays in the way of my reasoning by uncovering some hidden methods to the outer world.

Consider the next several traits implemented for a private type S and its derivatives: (I use modules for this example, not crates, but there’s no difference in behavior here)

trait Self_ {
    fn self_secret(&self);
}
trait Ref {
    fn ref_secret(&self);
}
trait Ptr {
    fn ptr_secret(&self);
}
trait Arr {
    fn arr_secret(&self);
}
trait Arr0 {
    fn arr0_secret(&self);
}
trait Arr1 {
    fn arr1_secret(&self);
}
trait Fn {
    fn fn_secret(&self);
}
trait TyParam {
    fn ty_param_secret(&self);
}

mod m {
    struct Priv;
    
    impl ::Self_ for Priv { fn self_secret(&self) {} }
    impl<'a> ::Ref for &'a Priv { fn ref_secret(&self) {} }
    impl ::Ptr for *const Priv { fn ptr_secret(&self) {} }
    impl ::Arr for [Priv] { fn arr_secret(&self) {} }
    impl ::Arr0 for [Priv; 0] { fn arr0_secret(&self) {} }
    impl ::Arr1 for [Priv; 1] { fn arr1_secret(&self) {} }
    impl ::Fn for fn(Priv) { fn fn_secret(&self) {} }
    impl ::TyParam for Option<Priv> { fn ty_param_secret(&self) {} }
}

fn main() {
    // ???.self_secret();
    // ???.ref_secret();
    (0 as *const _).ptr_secret();
    [].arr_secret();
    [].arr0_secret();
    // ???.arr1_secret();
    // ???.fn_secret();
    None.ty_param_secret();
}

As you can see, the methods of a trait can be called if the trait is implemented for pointers or empty arrays or unsized arrays of a private type. However, I haven’t found a way to call methods from impls for the private type itself or for references to it.

Can someone familiar with type inference guarantee that these methods can’t indeed be called from outside of module m (without involving trait objects) or give counterexamples if that is not true?


#2

The function case is like the others, just find a generic function with the same signature:

(drop as fn(_)).fn_secret();

#3

You can get the array case to compile but it will of course panic at runtime:

fn anyarray<T>() -> [T; 1] { panic!() }

anyarray().arr1_secret();

But type inference does not work out if you try to expand it to this:

/// This does not help
fn anything<T>() -> T { panic!() }

#4

@bluss
Excellent, thanks! Runtime panic doesn’t matter since the link time errors I want to prevent happen earlier.

Though, I mostly interested in impls for Priv, &Priv and &mut Priv (i.e. self_secret/ref_secret) since they have the largest practical impact. The trick with panic!() doesn’t work for them.


#5

Once you have an array of Priv it’s easy to get a Priv from it:

fn anyarray<T>() -> [T; 1] { panic!() }
let arr = anyarray();
arr.arr1_secret(); // Needed for type inferrence

(&arr[0]).ref_secret();
arr[0].self_secret();

#6

Damn. That’s seriously unfortunate.


#7

I think it would be reasonable to prohibit such super contrived cases through the privacy system to keep sanity for regular code. I’ll try to come up with something.


#8

Yes, I don’t think any of the type inferences here are reasonable — the type should not be exposed at all.


#9

Maybe just reject selections that introduce cross-crate private structs? I would prefer to talk about this with @nikomatsakis though.


#10

I’m very ignorant of rustc internals, but Priv being private, the type or its trait impls shouldn’t necessarily need to be known or exposed at all in the crate metadata.