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

Deref coercion from &DST to &TraitObject #2239

Closed
dtolnay opened this issue Dec 8, 2017 · 9 comments
Closed

Deref coercion from &DST to &TraitObject #2239

dtolnay opened this issue Dec 8, 2017 · 9 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@dtolnay
Copy link
Member

dtolnay commented Dec 8, 2017

A dynamically sized type cannot currently be passed as a trait object.

error[E0277]: the trait bound `str: std::marker::Sized` is not satisfied
  |
6 |     f("str");
  |       ^^^^^ `str` does not have a constant size known at compile-time
  |
  = note: required for the cast to the object type `std::fmt::Debug`

In such cases the &DST should perform a deref coercion, equivalent to doing &&DST as &TraitObject.

use std::fmt::Debug;

fn f(_trait_object: &Debug) {}

fn main() {
    f("str"); // does not compile today. would coerce to &&str
    f(&[1][..]); // does not compile today. would coerce to &&[{integer}]
}

We hit this in rust-lang/rust#46445 in which a slice cannot be passed to DebugStruct::field. I have also hit this in Serde with the Expected trait object, which commonly requires passing an argument that looks like &"message".

@SimonSapin
Copy link
Contributor

In such cases the &DST should perform a deref coercion, equivalent to doing &&DST as &TraitObject.

Isn’t that autoref rather than auto-deref? If I remember correctly so far we only have autoref in method calls.

@dtolnay
Copy link
Member Author

dtolnay commented Dec 8, 2017

I meant coercion just in that the caller writes an expression that looks like it has type &U but the callee accepts it as type &T. In this case U=str and T=Debug. The autoref aspect of it is an implementation detail, unlike in method calls. From the language point of view the caller passes a &DST and the callee receives a &TraitObject, exactly as it would have worked for passing a sized type.

@eddyb
Copy link
Member

eddyb commented Dec 8, 2017

@dtolnay But &DST: Trait and not DST: Trait. T and &T impls aren't required to be equivalent.

@dtolnay
Copy link
Member Author

dtolnay commented Dec 8, 2017

@eddyb does that make it hopeless? Maybe "equivalent to doing &&DST as &TraitObject" is oversimplifying but "insert an extra & to eliminate the papercut" still seems promising to me.

struct TraitObject {
    data: *const (),
    // fn(&self)
    vtable: fn(*const ()),
}

fn callee(trait_object: &TraitObject) {
    (trait_object.vtable)(trait_object.data)
}

fn main() {
    // callee("str")
    callee(&TraitObject { data: &"str" as *const &str as *const (), vtable: impl_trait_for_str });

    // callee(&"str")
    callee(&TraitObject { data: &"str" as *const &str as *const (), vtable: impl_trait_for_ref_str });
}

// impl Trait for str {}
fn impl_trait_for_str(data: *const ()) {
    let s = unsafe { &**(data as *const *const str) };
    assert!(s.chars().next().unwrap() == 's');
    println!("impl Trait for str");
}

// impl Trait for &str {}
fn impl_trait_for_ref_str(data: *const ()) {
    let s = unsafe { &**(data as *const *const str) };
    assert!(s.chars().next().unwrap() == 's');
    println!("impl Trait for &str");
}

@eddyb
Copy link
Member

eddyb commented Dec 8, 2017

Note that the existing coercion doesn't distinguish between &T and *T and preserves the ownership semantics by rebuilding whatever structures were around the pointer (usually raw).
Do you expect no coercion to occur when Box or Rc are involved instead of &?

@dtolnay
Copy link
Member Author

dtolnay commented Dec 8, 2017

The places we have had trouble with this (in std::fmt and Serde) would be resolved if the change only applies to &T, not Box or Rc.

@SimonSapin
Copy link
Contributor

I think the argument against pervasive autoref was to avoid a situation where a value looks like it is moved but it is in fact borrowed. However borrowing implicitly is not a problem when the value is already a &T borrow. The result is &&T which doesn’t seem useful, except in this very specific situation.

This is a long way to say 👍

@dtolnay
Copy link
Member Author

dtolnay commented Dec 8, 2017

I don't think I have bandwidth to pursue this in the near term. @GuillaumeGomez would you be interested in putting together a full RFC?

@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Dec 8, 2017
@GuillaumeGomez
Copy link
Member

I actually have no idea how such a feature could be implemented so I don't think I'm the one to pick for this task.

@dtolnay dtolnay closed this as completed May 26, 2024
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 RFC.
Projects
None yet
Development

No branches or pull requests

5 participants