Description
One of unsafe trait DerefPure
's preconditions is that Self
's Deref
impl is "well-behaved". std::borrow::Cow
currently always implements DerefPure
, but Cow<B>
's Deref
impl can call arbitrary user code in <B::Owned as Borrow<B>>::borrow
that may not be "well-behaved".
I tried this (safe) code:
#![feature(deref_patterns)]
use std::borrow::{Cow, Borrow};
struct Weird<T: ?Sized = [()]>(bool, T);
struct WeirdOwned {
val: std::cell::Cell<bool>,
}
impl Borrow<Weird> for WeirdOwned {
fn borrow(&self) -> &Weird {
let val = self.val.get();
self.val.set(!val);
if val { &Weird(true, []) } else { &Weird(false, []) }
}
}
impl ToOwned for Weird {
type Owned = WeirdOwned;
fn to_owned(&self) -> WeirdOwned {
WeirdOwned {
val: false.into(),
}
}
}
fn main() {
let x: Cow<Weird> = Cow::Owned(WeirdOwned { val: true.into() });
match x {
deref!(Weird(false, _)) | deref!(Weird(true, _)) => println!("a"),
_ => println!("b"),
}
// prints b
let x: Cow<Weird> = Cow::Owned(WeirdOwned { val: true.into() });
match x {
deref!(Weird(false, _) | Weird(true, _)) => println!("a"),
_ => println!("b"),
}
// prints a
}
I expected to see this happen: Either this shouldn't compile (because Cow<Weird>
's Deref
impl is not "well-behaved"), or the first match should also print "a".
Instead, this happened: Having two separate deref!
patterns joined by an or-pattern behaves differently than an or-pattern in a deref!
pattern. IIUC currently there is no soundness issue because deref patterns do not interact with exhaustiveness checking, but if the first match didn't require the blanket _
arm and considered deref!(Weird(false, _)) | deref!(Weird(true, _))
exhaustive, then the exhibited behavior would be a soundness issue.
One possible resolution might be to restrict Cow
's DerefPure
impls to just Cow<T: Sized>
, Cow<str>
, Cow<[T]>
, and other types that the stdlib fully controls the ToOwned
/Borrow
/Deref
impls of.
-unsafe impl<B: ?Sized + ToOwned> DerefPure for Cow<'_, B> where B::Owned: Borrow<B> {}
+unsafe impl<T: Clone> DerefPure for Cow<'_, T>{}
+unsafe impl DerefPure for Cow<'_, str>{}
+unsafe impl<T: Clone> DerefPure for Cow<'_, [T]>{}
incorrect suggestion
Moved this into a hidden block because this would not work with Cow<T: Sized>
, since T::Owned = T
, which probably doesn't implement Deref<Target = T> + DerefPure
.
One possible resolution might be to add "
Borrow<Self::Target>
/BorrowMut<Self::Target>
are well-behaved if implemented" toDerefPure
's preconditions, and then changeCow
's impl as follows:-unsafe impl<B: ?Sized + ToOwned> DerefPure for Cow<'_, B> where B::Owned: Borrow<B> {} +unsafe impl<B: ?Sized + ToOwned> DerefPure for Cow<'_, B> where B::Owned: Borrow<B> + Deref<Target = B> + DerefPure {}Then,
Cow<Weird>
would not implementDerefPure
, becauseWeirdOwned
does not implementDerefPure
. This would still allowCow<str>
, andCow<[T]>
(sinceString
andVec<T>
implementDerefPure
), but this would not work withCow<T: Sized>
, sinceT::Owned = T
, which might not implementDerefPure
, and probably doesn't implementDeref<Target = T>
.Note that just adding
B::Owned: DerefPure
is not sufficient, since you could haveWeird::Owned = Box<WeirdOwned>
, andBox: DerefPure
.
Meta
rustc --version --verbose
:
rustc 1.86.0-nightly (649b995a9 2025-01-22)
binary: rustc
commit-hash: 649b995a9febd658b2570160703dff6fdc038ab2
commit-date: 2025-01-22
host: x86_64-unknown-linux-gnu
release: 1.86.0-nightly
LLVM version: 19.1.7
@rustbot label +F-deref-patterns +requires-nightly