Use unsafe to close traits


#1

The trait UnsignedInt says “A built-in unsigned integer.

Maybe we should use unsafe trait to “enforce” this property? That’s what I have realized in my own code — I can use unsafe traits when I want to ensure that a user can’t mess up my assumptions by implementing the trait for their external types. Usually it’s a completely hypothetical scenario, yet legal in safe rust and thus I’m “liable”.

Idea: use unsafe trait for UnsignedInt, SignedInt because the traits are effectively closed: no more implementations should be added. This increases their utility: You know only the right types implement it.


#2

I’d go the other direction and get rid of the “a built-in” part of that description. There’s no reason a user shouldn’t be able to implement a u128, BigUnsignedInt, etc.


#3

Then it’s not the same trait. I know the intention is a non-extensible trait in this case.

Maybe it’s a pretty silly idea… if I need the guarantees I could make my own unsafe version of UnsignedInt – in this case.


#4

Unsafe traits. Do you mean unsafe impl UnsignedInt for MyInt is still allowed?


#5

Unsafe isn’t really the way to go here. One way to fix this is to allow public traits to depend on private traits:

trait Private { }
pub trait Public: Private { }

To avoid putting trait Private { } statements everywhere, rust could implicitly define DefinedInCrate and DefinedInModule traits on a per crate/module basis and implement them on all types defined in acrate/module. However, this system is a little less flexible. However, those would effectively be path-dependent types and I don’t know if rust really wants those.


#6

I think you are right about that, but that requires a language change. The Idea in the topic was just about a small change of a thing in libstd.

But you are right. We need to be able to use private traits like that, it solves another problem: “overloading”.

pub trait AcceptableType {
  ...
}

impl Foo {
pub fn new_from<T: AcceptableType>(init: T) -> Self { .. }
}

There are two choices here: AcceptableType is a regular, open trait, and users may usefully impl it for their own types sometimes. The other simpler choice is to make it a closed/private trait.

The closed/private trait is simpler: less public API surface area. I can change the AcceptableType’s methods when refactoring my library. All I used the trait for was for user-facing constructor overload anyway.

Using unsafe trait is not even a solution there – the public trait adds methods to the types it is implemented for. I don’t want that with a closed trait, I don’t want that just because the trait is part of the public API, that its internals and its methods are available to library users. I want that to be an encapsulated detail in my API. All I wanted was overloading of new_from.


#7

Could we introduce an attribute for this?

#[deny_impl_outside_this_crate]
pub trait Int { ... }

#8

The integer traits were built for: Have functions on primitives, allow minimal genericity over primitives.

No code “assumes” properties of these. Nothing breaks if you try to use them on things they weren’t made for. They’re just bad at doing “general” numbers. I don’t see a reason to try to actively prevent people from using a mediocre trait for their BigInt or whatever.


#9

Ok but then the description of the trait is wrong. And I know that numerics in libstd is explicitly not for generalizing over number types, so I was assuming all along it was what it says, a trait for built-in unsigned integer types.