Trait visibilities: cannot call implemented function on an variable, when the trait is "sealed"

I'm using ryu crate, and I found that I cannot call is_nonfinite on a float variable:

let f : f64 = 1.234;
 f.is_nonfinite();

I thought the reason is that is_nonfinite is implement from Sealed trait, which is private.

But if I write like this:

fn is_infinite<F: Float>(f: F) -> bool {
    f.is_nonfinite()
}

...

let f : f64 = 1.234;
is_infinite(f);

then it works.

I wondered that why I can "see" is_nonfinite in a generic function with type bounds, but cannot use it in a normal function?

P.S. my fully code:

use ryu::Float;

fn is_infinite<F: Float>(f: F) -> bool {
    f.is_nonfinite()
}

fn main() {
    let f : f64 = 1.234;
    is_infinite(f);
    f.is_nonfinite();
}

In addition to use statements, trait bounds on generic parameters also make the trait's methods visible on the bounded type, e.g. this also compiles:

// NO `use ryu::Float`

fn is_infinite<F: ryu::Float>(f: F) -> bool {
    f.is_nonfinite()
}

I guess because trait bounds implicitly also require that the type implements any super-traits, they make any methods from the super-traits visible as well. However, it seems surprising, and possibly unintended, that this would apply to inacessible super-traits as well.

I think these methods are not supported to be used. The reason why the generic function with bounds allows you to call the method is because there are special rules for such functions that bring methods into scope. This is mostly useful if you have a <T: qualified::path::to::Trait> kind of bound in order to allow method syntax for the methods of the trait qualified::path::to::Trait inside of the function without the need for a use statement. This feature is - admitted - a bit rough and potentially weird, I remember seeing other quirks of it in the past, too (I can't think of the details right now). As you can see, it does seem to include supertraits, too.

The Sealed trait in ryu is a public-in-private trait. I don't think the trait author is aware that the methods can still be called as you demonstrated and in any case they are, as I already mentioned, most likely not supposed to be called and subject to change in the future in minor version updates. Note, that the methods aren't documented anywhere either. Sometimes we're providing actually public but doc(hidden) API in some crates, too, (for macro-internal usage), and this API isn't supposed to be used either and subject to change, indicated just by the fact that it doesn't appear in documentation at all.

2 Likes

Ping @dtolnay, so that now they are aware and can comment themselves.

1 Like

So when talking about this implicitly import rule, in this case, bringing hidden API visible, an intentional behaviour, or a bug, or an UB?

And any document documents illustrate this rule? I searched the reference doc but didn't find, maybe I'm not using a technical keyword.

From the page on method call expressions:

Then, for each candidate type T , search for a visible method with a receiver of that type in the following places:

  1. T 's inherent methods (methods implemented directly on T ).
  2. Any of the methods provided by a visible trait implemented by T . If T is a type parameter, methods provided by trait bounds on T are looked up first. Then all remaining methods in scope are looked up.

The use of "visible" there does suggest that this behaviour with inaccessible supertraits is unintended.

3 Likes

Thanks for helping, maybe I should report an issue and let the rust members to decide...

Related existing issue: #83882

1 Like

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.