Skip to content

DerefPure impl for Cow may be too general #136046

New issue

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

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

Already on GitHub? # to your account

Closed
zachs18 opened this issue Jan 25, 2025 · 4 comments · Fixed by #137105
Closed

DerefPure impl for Cow may be too general #136046

zachs18 opened this issue Jan 25, 2025 · 4 comments · Fixed by #137105
Labels
C-bug Category: This is a bug. F-deref_patterns `#![feature(deref_patterns)]` requires-nightly This issue requires a nightly compiler in some way. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@zachs18
Copy link
Contributor

zachs18 commented Jan 25, 2025

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" to DerefPure's preconditions, and then change Cow'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 implement DerefPure, because WeirdOwned does not implement DerefPure. This would still allow Cow<str>, and Cow<[T]> (since String and Vec<T> implement DerefPure), but this would not work with Cow<T: Sized>, since T::Owned = T, which might not implement DerefPure, and probably doesn't implement Deref<Target = T>.

Note that just adding B::Owned: DerefPure is not sufficient, since you could have Weird::Owned = Box<WeirdOwned>, and Box: 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

@zachs18 zachs18 added the C-bug Category: This is a bug. label Jan 25, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jan 25, 2025
@jieyouxu jieyouxu added T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. requires-nightly This issue requires a nightly compiler in some way. F-deref_patterns `#![feature(deref_patterns)]` and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Jan 25, 2025
@jieyouxu
Copy link
Member

Backlink: tracking issue #87121

@jieyouxu jieyouxu added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Jan 25, 2025
@Nadrieril
Copy link
Member

You're absolutely right, I had not noticed that Cow's Deref used borrow under the hood. I'm in favor of implementing it for some known safe types.

@joseph-gio
Copy link
Contributor

Might make sense to add a BorrowPure/TrustedBorrow trait to ensure that the borrow implementation is well-behaved?

@Nadrieril
Copy link
Member

The impls suggested in the OP (for T: Clone and for Vec/String) seem sufficient for now, we can always add more later.

workingjubilee added a commit to workingjubilee/rustc that referenced this issue Feb 16, 2025
…Nadrieril

Restrict DerefPure for Cow<T> impl to T = impl Clone, [impl Clone], str.

Fixes rust-lang#136046

`feature(deref_patterns)` tracking issue: rust-lang#87121

`Cow<'_, T>` should only implement `DerefPure` if its `Deref` impl is pure, which requires `<T::Owned as Borrow<T>>::borrow`  to be pure. This PR restricts `impl DerefPure for Cow<'_, T>` to `T: Sized + Clone`, `T = [U: Clone]`, and `T = str` (for all of whom `<T::Owned as Borrow<T>>::borrow` is implemented in the stdlib and is pure).

cc `@Nadrieril`

------

An alternate approach would be to introduce a new `unsafe trait BorrowPure<T>` analogous to `DerefPure`  that could be implemented for `T: Sized`, `&T`, `&mut T`, `String`, `Vec`, `Box`, `PathBuf`, `OsString`, etc. rust-lang/rust@master...zachs18:borrow-pure-trait
@bors bors closed this as completed in fae72a0 Feb 17, 2025
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Feb 17, 2025
Rollup merge of rust-lang#137105 - zachs18:cow-derefpure-restrict, r=Nadrieril

Restrict DerefPure for Cow<T> impl to T = impl Clone, [impl Clone], str.

Fixes rust-lang#136046

`feature(deref_patterns)` tracking issue: rust-lang#87121

`Cow<'_, T>` should only implement `DerefPure` if its `Deref` impl is pure, which requires `<T::Owned as Borrow<T>>::borrow`  to be pure. This PR restricts `impl DerefPure for Cow<'_, T>` to `T: Sized + Clone`, `T = [U: Clone]`, and `T = str` (for all of whom `<T::Owned as Borrow<T>>::borrow` is implemented in the stdlib and is pure).

cc ``@Nadrieril``

------

An alternate approach would be to introduce a new `unsafe trait BorrowPure<T>` analogous to `DerefPure`  that could be implemented for `T: Sized`, `&T`, `&mut T`, `String`, `Vec`, `Box`, `PathBuf`, `OsString`, etc. rust-lang/rust@master...zachs18:borrow-pure-trait
github-actions bot pushed a commit to tautschnig/verify-rust-std that referenced this issue Mar 11, 2025
…Nadrieril

Restrict DerefPure for Cow<T> impl to T = impl Clone, [impl Clone], str.

Fixes rust-lang#136046

`feature(deref_patterns)` tracking issue: rust-lang#87121

`Cow<'_, T>` should only implement `DerefPure` if its `Deref` impl is pure, which requires `<T::Owned as Borrow<T>>::borrow`  to be pure. This PR restricts `impl DerefPure for Cow<'_, T>` to `T: Sized + Clone`, `T = [U: Clone]`, and `T = str` (for all of whom `<T::Owned as Borrow<T>>::borrow` is implemented in the stdlib and is pure).

cc ``@Nadrieril``

------

An alternate approach would be to introduce a new `unsafe trait BorrowPure<T>` analogous to `DerefPure`  that could be implemented for `T: Sized`, `&T`, `&mut T`, `String`, `Vec`, `Box`, `PathBuf`, `OsString`, etc. rust-lang/rust@master...zachs18:borrow-pure-trait
github-actions bot pushed a commit to tautschnig/verify-rust-std that referenced this issue Mar 11, 2025
…Nadrieril

Restrict DerefPure for Cow<T> impl to T = impl Clone, [impl Clone], str.

Fixes rust-lang#136046

`feature(deref_patterns)` tracking issue: rust-lang#87121

`Cow<'_, T>` should only implement `DerefPure` if its `Deref` impl is pure, which requires `<T::Owned as Borrow<T>>::borrow`  to be pure. This PR restricts `impl DerefPure for Cow<'_, T>` to `T: Sized + Clone`, `T = [U: Clone]`, and `T = str` (for all of whom `<T::Owned as Borrow<T>>::borrow` is implemented in the stdlib and is pure).

cc ``@Nadrieril``

------

An alternate approach would be to introduce a new `unsafe trait BorrowPure<T>` analogous to `DerefPure`  that could be implemented for `T: Sized`, `&T`, `&mut T`, `String`, `Vec`, `Box`, `PathBuf`, `OsString`, etc. rust-lang/rust@master...zachs18:borrow-pure-trait
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
C-bug Category: This is a bug. F-deref_patterns `#![feature(deref_patterns)]` requires-nightly This issue requires a nightly compiler in some way. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants