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

`where_clauses_object_safety` future compatibility lint #51443

Open
leodasvacas opened this Issue Jun 8, 2018 · 12 comments

Comments

Projects
None yet
@leodasvacas
Contributor

leodasvacas commented Jun 8, 2018

This is the summary issue for the WHERE_CLAUSES_OBJECT_SAFETY
future-compatibility warning and other related errors. The goal of
this page is describe why this change was made and how you can fix
code that is affected by it. It also provides a place to ask questions
or register a complaint if you feel the change should not be made. For
more information on the policy around future-compatibility warnings,
see our breaking change policy guidelines.

What is the warning for?

As was discovered in #50781 a combination of implementing a trait directly for a dyn type and where clauses involving Self can punch a hole in our dyn-capability rules rules. See the minimization:

trait Trait {}

trait X { fn foo(&self) where Self: Trait; }

impl X for () { fn foo(&self) {} }

impl Trait for dyn X {}

// Segfault at opt-level 0, SIGILL otherwise.
pub fn main() { <X as X>::foo(&()); }

The fix applied in #50966 is to tighten the dyn-capability rules to make X not dyn-capable in this case, in general making any trait that contains Self in where clauses not dyn-capable, much like we disallow Self in arguments or return type. Few root regressions appeared in the crater run and those were fixable, though some crates being unmaintained complicates things.

However that fix is heavy handed and disallows things we actually wish to support, still we went ahead with the warning as a stop gap while we look for a better, more targeted fix. The original issue contains some discussion of alternative fixes. With tight chalk integration, we could do some clever things.

Other alternatives discussed for the short-term:

  • Checking from an impl Foo for dyn Bar + .. whether that impl is causing trouble. This seems to be a dead end because it's hard to tell which dyn types are targeted by a blanket impl such as impl<U: ?Sized + Bounds> Trait for U.
  • Checking the cast sites with a rule like "if dyn Foo: Trait then to cast T into dyn Foo we require that T: Trait must also hold", probably for every Trait such that Self: Trait appears in where clauses in the traits methods. This would alleviate the warning for Self: Trait where clauses. This may be practical to implement now, but seems subtle, we'd need to give it more thought.

When will this warning become a hard error?

Hopefully we will develop a finer-grained rule and this warning will never be an error.

How to fix this?

Either drop the where clause or stop using dyn types with the affected trait. If this is not viable, that's probably ok, it's very unlikely that your code is actually unsound. But please do report your case here so take we may take it into consideration and see how to better support it!

@nikomatsakis

This comment has been minimized.

Contributor

nikomatsakis commented Jun 25, 2018

To clarify: we are specifically interested in getting feedback on places where this lint causes you issues, since that will help us to judge how well more precise fixes would work.

@cramertj

This comment has been minimized.

Member

cramertj commented Jun 27, 2018

This caused a future-compat warning in the upcoming futures 0.3 crate. Minimized example:

#![feature(arbitrary_self_types, pin)]

use std::marker::Unpin;
use std::mem::PinMut;

trait Future {
    fn poll(self: PinMut<Self>);
}

trait FutureExt: Future {
    fn poll_unpin(&mut self) where Self: Unpin {
        PinMut::new(self).poll()
    }
}

The error:

warning: the trait `FutureExt` cannot be made into an object
  --> foo.rs:11:5
   |
11 | /     fn poll_unpin(&mut self) where Self: Unpin {
12 | |         PinMut::new(self).poll()
13 | |     }
   | |_____^
   |
   = note: #[warn(where_clauses_object_safety)] on by default
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #51443 <https://github.com/rust-lang/rust/issues/51443>
   = note: method `poll_unpin` references the `Self` type in where clauses

It seems like it should be possible to permit calls to poll_unpin in cases where Self is a dyn Trait type where Trait: Unpin.

@nikomatsakis

This comment has been minimized.

Contributor

nikomatsakis commented Jun 28, 2018

Thanks @cramertj -- that falls pretty squarely into the rules we had in mind! Let's discuss the details of those rules in #50781 I guess, I want to try and "re-sync" (but I guess I'd rather keep this thread for people to report issues).

But just to summarize, the rule we had in mind is roughly this:

  • If a trait Trait has a method method with a where-clause where Self: XXX
    • that is object-safe so long as dyn Trait: XXX cannot be proven
      • although it is a bit weird in the case where trait Trait: XXX -- we could perhaps try to distinguish those cases; in Chalk terms, XXX would be an implied bound of Trait

That applies to your example, I believe, since dyn Future: Unpin does not hold.

This is a kind of generalization of the rule around where Self: Sized -- except that, in that case, we know that dyn Trait: Sized could never be proven, and hence we can suspend the where-clause requirements entirely.

@locka99

This comment has been minimized.

locka99 commented Jul 13, 2018

For anyone reading this, I got the warning/error on a trait that was like this:

pub trait Config {
    fn save(&self, path: &Path) -> Result<(), ()> where Self: serde::Serialize { ... }
}

In my case I was able to make the problem go away by requiring the thing implementing Config to also implement serde::Serialize:

pub trait Config: serde::Serialize {
    fn save(&self, path: &Path) -> Result<(), ()> { ... }
}
@emilio

This comment has been minimized.

Contributor

emilio commented Jul 30, 2018

This broke bindgen:

error: the trait `ir::template::TemplateParameters` cannot be made into an object
   --> src/ir/template.rs:134:5
    |
134 | /     fn all_template_params(&self, ctx: &BindgenContext) -> Vec<TypeId>
135 | |     where
136 | |         Self: ItemAncestors,
137 | |     {
...   |
141 | |         }).collect()
142 | |     }
    | |_____^
    |
note: lint level defined here
   --> src/lib.rs:11:9
    |
11  | #![deny(warnings)]
    |         ^^^^^^^^
    = note: #[deny(where_clauses_object_safety)] implied by #[deny(warnings)]
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #51443 <https://github.com/rust-lang/rust/issues/51443>
    = note: method `all_template_params` references the `Self` type in where clauses

error: the trait `ir::template::TemplateParameters` cannot be made into an object
   --> src/ir/template.rs:147:5
    |
147 | /     fn used_template_params(&self, ctx: &BindgenContext) -> Vec<TypeId>
148 | |     where
149 | |         Self: AsRef<ItemId>,
150 | |     {
...   |
160 | |                     .collect()
161 | |     }
    | |_____^
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #51443 <https://github.com/rust-lang/rust/issues/51443>
    = note: method `used_template_params` references the `Self` type in where clauses

error: aborting due to 2 previous errors

error: Could not compile `bindgen`.

This is on this bindgen commit fwiw: rust-lang/rust-bindgen@29dad40

emilio added a commit to rust-lang/rust-bindgen that referenced this issue Jul 30, 2018

@Emerentius

This comment has been minimized.

Contributor

Emerentius commented Aug 1, 2018

I have an extension trait that was never supposed to be made into an object which triggers this. It only exists to add a few methods.
I could limit the implementors to just [T] down from T: AsRef<[T]> which would stop the lint from firing (and also be a better design), but it would also make the dyn capability pointless as there's only one type that could be made into the object.

I like this as a lint but the it will become a hard error in a future release! part of the error message should go. Not all traits require dyn capability.

Edit: In fact, the lint is a false positive. The trait isn't dyn capable already because it contains generic functions.

@alecmocatta

This comment has been minimized.

Contributor

alecmocatta commented Aug 3, 2018

I bumped into this upcasting boxed trait objects with marker traits. There may be a way to do this safely that doesn't fall foul of this lint?

use std::any::Any;

trait Trait: Any {
	fn into_any(self: Box<Self>) -> Box<Any>;
	fn into_any_send(self: Box<Self>) -> Box<Any+Send> where Self: Send;
	fn into_any_sync(self: Box<Self>) -> Box<Any+Sync> where Self: Sync;
	fn into_any_send_sync(self: Box<Self>) -> Box<Any+Send+Sync> where Self: Send+Sync;
}

impl<T> Trait for T where T: Any {
	fn into_any(self: Box<Self>) -> Box<Any> {
		self
	}
	fn into_any_send(self: Box<Self>) -> Box<Any+Send> where Self: Send {
		self
	}
	fn into_any_sync(self: Box<Self>) -> Box<Any+Sync> where Self: Sync {
		self
	}
	fn into_any_send_sync(self: Box<Self>) -> Box<Any+Send+Sync> where Self: Send+Sync {
		self
	}
}

fn main() {
	let a: usize = 123;
	let b: Box<Trait+Send+Sync> = Box::new(a);
	let c: Box<Any+Send+Sync> = b.into_any_send_sync();
	let _d: usize = *Box::<Any>::downcast(c).unwrap();
}

(Playground)

@passcod

This comment has been minimized.

Contributor

passcod commented Aug 24, 2018

This warns on AnyMap, but I don't understand enough to really figure out what's going on and how it should be fixed:

warning: the trait `anymap::any::CloneToAny` cannot be made into an object
  --> /home/.cargo/registry/src/github.com-1ecc6299db9ec823/anymap-0.12.1/src/any.rs:21:5
   |
21 |     fn clone_to_any_send_sync(&self) -> Box<CloneAny + Send + Sync> where Self: Send + Sync;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #51443 <https://github.com/rust-lang/rust/issues/51443>
   = note: method `clone_to_any_send_sync` references the `Self` type in where clauses
@rodrimati1992

This comment has been minimized.

rodrimati1992 commented Sep 16, 2018

I have an extension trait that i want to be usable with ?Sized types (including str) where this is causing a problem.

This is the reduced version of the trait:

trait SelfOps{
    fn piped_ref<'a,F, U>(&'a self, f: F) -> U
    where
        F: FnOnce(&'a Self) -> U,
    {
        f(self)
    }

    fn format_debug(&self)->String
    where Self:fmt::Debug
    {
        format!("{:?}",self)
    }
}

impl<This:?Sized> SelfOps for This{}

With the previous 2 methods I get this somewhat confusing pair of warning and error :


warning: the trait `SelfOps` cannot be made into an object
  --> src/main.rs:11:5
   |
11 | /     fn format_debug(&self)->String
12 | |     where Self:fmt::Debug
13 | |     {
14 | |         format!("{:?}",self)
15 | |     }
   | |_____^
   |
   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
   = note: for more information, see issue #51443 <https://github.com/rust-lang/rust/issues/51443>
   = note: method `format_debug` references the `Self` type in where clauses

error[E0038]: the trait `SelfOps` cannot be made into an object
  --> src/main.rs:23:18
   |
23 |     let x=&() as &SelfOps;
   |                  ^^^^^^^^ the trait `SelfOps` cannot be made into an object
   |
   = note: method `piped_ref` has generic type parameters

It seems to me that if it knows that it can't be turned into a trait object because it has a generic method,
then it should not warn because of the Self type in the where clause of format_debug.

@chris-morgan

This comment has been minimized.

Member

chris-morgan commented Sep 17, 2018

On the AnyMap case: it’s substantially the same as what @alecmocatta is reporting. This lint causes trouble with auto traits.

core::any::Any has the ability to be Any + Send, and Any + Send + Sync, and that’s OK because it doesn’t need any support inside the trait object for code specific to Send and/or Sync functionality—just an impl Any + Send block.

AnyMap has an extension of that, a cloneable Any. That part is fine, but when you get to additional trait bounds, you need a function that will be in the vtable; and that’s where we run into trouble under this new scheme.

Here’s a simplified version of some of the code (covering only the Send bound):

#[doc(hidden)]
pub trait CloneToAny {
    /// Clone `self` into a new `Box<CloneAny>` object.
    fn clone_to_any(&self) -> Box<CloneAny>;

    /// Clone `self` into a new `Box<CloneAny + Send>` object.
    fn clone_to_any_send(&self) -> Box<CloneAny + Send> where Self: Send;
}

impl<T: Any + Clone> CloneToAny for T {
    fn clone_to_any(&self) -> Box<CloneAny> {
        Box::new(self.clone())
    }

    fn clone_to_any_send(&self) -> Box<CloneAny + Send> where Self: Send {
        Box::new(self.clone())
    }
}

pub trait CloneAny: Any + CloneToAny { }

impl<T: Any + Clone> CloneAny for T { }

impl Clone for Box<CloneAny> {
    fn clone(&self) -> Box<CloneAny> {
        (**self).clone_to_any()
    }
}

impl Clone for Box<CloneAny + Send> {
    fn clone(&self) -> Box<CloneAny + Send> {
        (**self).clone_to_any_send()
    }
}

Now as it stands, I know that if I have a Box<CloneAny + Send>, then I know that calling clone_to_any_send is safe.

I would like to retain the same interface: adding trait bounds is a known and sane way of handling these things—the way you’re supposed to handle it. But if necessary, I suppose the proliferation of traits can begin, with CloneAny, CloneSendAny, CloneSendSyncAny, &c.

As it stands, I’m seeing a few options for me as a developer:

  1. Bury my head in the sand and hope the problem goes away without my doing anything (if I’m feeling particularly foolishly bull-headed, which I’m not at present, add #[allow(where_clauses_object_safety)]!);
  2. Drop the clone_to_any_* methods, leaving only clone_to_any, then cross my fingers, make the Clone implementation transmute between Box<CloneAny> and Box<CloneAny + Send>, and hope that the vtables are the same and that nothing will explode;
  3. Appeal to see if it’s reasonable to the lint can be disabled specifically for auto traits (that is, if my existing code can be deemed acceptable after all);
  4. Stop using additional bounds, and proliferate traits.

Expanding on the appeal: I don’t know, but my impression is that auto traits won’t trigger the memory unsafety problem. If so, is it reasonable to check whether the additional bounds on Self are all auto traits, and consider that acceptable code?

And if tweaking the lint is not reasonable, is what I outline in the second item reasonable? Will Rust eat my laundry if I transmute Box<CloneAny> and Box<CloneAny + Send>?

(Quite incidentally: where I’m from “laundry” refers to the room where clothes washing is done, not to the pile of clothes, which is called the “washing”. The whole “may eat your laundry” thing was therefore much more amusing to contemplate than it would have been if it were mere clothes that were being devoured.)

@alecmocatta

This comment has been minimized.

Contributor

alecmocatta commented Sep 17, 2018

And if tweaking the lint is not reasonable, is what I outline in the second item reasonable? Will Rust eat my laundry if I transmute Box<CloneAny> and Box<CloneAny + Send>?

This is effectively what's done by the stdlib here:

rust/src/liballoc/boxed.rs

Lines 455 to 517 in f1aefb4

impl Box<dyn Any> {
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
/// Attempt to downcast the box to a concrete type.
///
/// # Examples
///
/// ```
/// use std::any::Any;
///
/// fn print_if_string(value: Box<Any>) {
/// if let Ok(string) = value.downcast::<String>() {
/// println!("String ({}): {}", string.len(), string);
/// }
/// }
///
/// fn main() {
/// let my_string = "Hello World".to_string();
/// print_if_string(Box::new(my_string));
/// print_if_string(Box::new(0i8));
/// }
/// ```
pub fn downcast<T: Any>(self) -> Result<Box<T>, Box<dyn Any>> {
if self.is::<T>() {
unsafe {
let raw: *mut dyn Any = Box::into_raw(self);
Ok(Box::from_raw(raw as *mut T))
}
} else {
Err(self)
}
}
}
impl Box<dyn Any + Send> {
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
/// Attempt to downcast the box to a concrete type.
///
/// # Examples
///
/// ```
/// use std::any::Any;
///
/// fn print_if_string(value: Box<Any + Send>) {
/// if let Ok(string) = value.downcast::<String>() {
/// println!("String ({}): {}", string.len(), string);
/// }
/// }
///
/// fn main() {
/// let my_string = "Hello World".to_string();
/// print_if_string(Box::new(my_string));
/// print_if_string(Box::new(0i8));
/// }
/// ```
pub fn downcast<T: Any>(self) -> Result<Box<T>, Box<dyn Any + Send>> {
<Box<dyn Any>>::downcast(self).map_err(|s| unsafe {
// reapply the Send marker
Box::from_raw(Box::into_raw(s) as *mut (dyn Any + Send))
})
}
}

@leodasvacas

This comment has been minimized.

Contributor

leodasvacas commented Sep 17, 2018

my impression is that auto traits won’t trigger the memory unsafety problem. If so, is it reasonable to check whether the additional bounds on Self are all auto traits, and consider that acceptable code?

This is true, this lint can be tweaked to not fire if the bounds are all auto traits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment