Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider allowing non-object safe traits as types #42235

Closed
withoutboats opened this issue May 26, 2017 · 8 comments
Closed

Consider allowing non-object safe traits as types #42235

withoutboats opened this issue May 26, 2017 · 8 comments
Labels
T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@withoutboats
Copy link
Contributor

Today, we only allow you to use a trait as a type if that trait is object safe, because you cannot sanely instantiate the object unless the trait is object safe.

This is a bit odd, though! We let you implement traits for HashMap<f32, _>, even though you cannot instantiate a HashMap with f32 keys.

We could limit the object safety check to two cases:

  • Instantiations of these types
  • Attempts to access items of these types.
trait NotObjectSafe: Sized { }

trait SomeTrait {
    fn static_method();
    fn non_static_method(&self);
}

impl SomeTrait for NotObjectSafe {
    fn static_method() {
        println!("There's no object, why isn't this allowed?");
        println!("You could totally call this function just fine.");
    }
    
    fn non_static_method(&self) {
        println!("You could just never instantiate the object.");
        println!("So you could never call this method.");
    }
}

fn main() {
     NotObjectSafe::static_method() // Never instantiated, should be fine.
}

Similarly, we could allow you to omit associated types from the receiver of such impls (e.g. impl for Iterator, without specifying an item).

I actually have a motivating use case for this: I have types which are parameterized by a trait, which is used solely for static dispatch. The trait is never actually instantiated as an object, and making it object safe is very limiting.

@withoutboats withoutboats added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label May 26, 2017
@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jun 2, 2017

The problem is this:

fn foo<T: ?Sized + SomeTrait>() {
    T::static_method();
}

fn main() {
    foo::<SomeTrait>();
}

So, what we could do -- and we discussed this ad infinitum back in the day -- is to keep the current notion of "object-safe", but report an error when:

  • Accessing a "non-object-safe" member of a trait object (e.g., a generic method)
  • Non-object-safe traits (like SomeTrait) do not "implement themselves" (i.e., SomeTrait: SomeTrait would not be true, hence the call to foo() would be illegal)

This was the case at some point. It complicates the "base language" in various ways. For example, right now, MIR has no built-in notion of a "virtual method dispatch". Instead, if you have a variable foo: &SomeTrait and you do foo.non_static_method(), that is encoded in MIR as <SomeTrait as SomeTrait>::non_static_method(foo). This corresponds to (in the user's mental model) the idea that SomeTrait: SomeTrait always holds (if objects are allowed at all). I am reluctant to lose this property, I must admit, but it could be done.

If you really want static_method to behave like a traditional static-method, you might consider this setup:

trait SomeTrait {
    fn non_static_method(&self);
}

impl SomeTrait {
    fn static_method();
}

Now you can do SomeTrait::static_method() but there is only one implementation of it (it is an inherent method).

@nikomatsakis
Copy link
Contributor

I actually have a motivating use case for this: I have types which are parameterized by a trait, which is used solely for static dispatch. The trait is never actually instantiated as an object, and making it object safe is very limiting.

Can you say more about this use case? I don't quite understand.

@nikomatsakis
Copy link
Contributor

(To be clear, though, I am not in particular satisfied with the current setup. I'm just also not very enamored with a setup where SomeTrait: SomeTrait may not hold, but trait objects are possible.)

@withoutboats
Copy link
Contributor Author

withoutboats commented Jun 2, 2017

The problem is this:

Wouldn't it be enough to say that the Foo trait object doesn't implement Foo unless it is object safe? I want to call methods from another trait implemented for this trait object (very similar to your example, but polymorphic).

Can you say more about this use case? I don't quite understand.

cargonauts has a macro that looks like this:

resource MyResource {
     method Get in JsonApi;
}

Get is a trait (implemented by MyResource). Get also needs to (as a type) implement a trait called Method, that has only static functions. Its compiled to a representation like EndpointService<MyResource, Get, JsonApi>, which invokes a static function from Method.

@withoutboats
Copy link
Contributor Author

Your example demonstrates the problem fine: https://is.gd/LUymxo

In order to provide an inherent impl, the trait must be object safe. But I don't think that's necessary.

@nikomatsakis
Copy link
Contributor

@withoutboats argh, I thought I had responded to this, but I see that I didn't. So, would you be satisfied with some solution that permitted methods on non-object-safe trait types, but didn't permit such types to be used more generally? (For example, we might say that you can always have type Trait for any trait, but unless the trait is object safe, then Trait: Trait does not hold and you cannot create instances of the type Trait.)

@withoutboats
Copy link
Contributor Author

(For example, we might say that you can always have type Trait for any trait, but unless the trait is object safe, then Trait: Trait does not hold and you cannot create instances of the type Trait.)

Yes, that seems like exactly what I'm trying for.

@withoutboats
Copy link
Contributor Author

Closing in favor of this RFC rust-lang/rfcs#2027

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

2 participants