Skip to content

Cow<'_, T> should not implement AsRef<T> but AsRef<U> where T: AsRef<U> #98905

Closed as not planned
@JanBeh

Description

@JanBeh

I tried this code:

use std::borrow::Cow;
use std::path::Path;

fn func(_: impl AsRef<Path>) {}

fn main() {
    let s: &str = ".";
    let path: &Path = Path::new(&s);
    func(path);
    func(&path);
    func(Cow::Borrowed(path));
    func(s);
    func(&s);
    func(Cow::Borrowed(s));
}

I expected to see this happen: Code compiles and runs without error.

Instead, this happened:

error[E0277]: the trait bound `Cow<'_, str>: AsRef<Path>` is not satisfied
  --> src/main.rs:14:10
   |
14 |     func(Cow::Borrowed(s));
   |     ---- ^^^^^^^^^^^^^^^^ the trait `AsRef<Path>` is not implemented for `Cow<'_, str>`
   |     |
   |     required by a bound introduced by this call
   |
   = help: the following other types implement trait `AsRef<T>`:
             <Cow<'_, OsStr> as AsRef<Path>>
             <Cow<'_, T> as AsRef<T>>
note: required by a bound in `func`
  --> src/main.rs:4:17
   |
4  | fn func(_: impl AsRef<Path>) {}
   |                 ^^^^^^^^^^^ required by this bound in `func`

For more information about this error, try `rustc --explain E0277`.

More examples of (allegedly) inconsistent behavior can be found in this comment below. The cause is subtle and I suspect it is in std. I believe that

impl<T: ?Sized + ToOwned> AsRef<T> for Cow<'_, T> {
    fn as_ref(&self) -> &T {
        self
    }
}

should actually be:

impl<T, U> AsRef<U> for Cow<'_, T>
where
    T: ?Sized + ToOwned + AsRef<U>,
    U: ?Sized,
{
    fn as_ref(&self) -> &U {
        self.deref().as_ref()
    }
}

That is because .as_ref() can't generally be used to go from T to &T. It doesn't need to go from Cow<'_, T> to &T either. We have .borrow() for that, because there is an impl<T: ?Sized> Borrow<T> for T in std.

Instead, Cow should be transparent in regard to AsRef, i.e. if a type T implements AsRef<U>, then Cow<'_, T> should implement AsRef<U> too. Compare &T, which implements AsRef<U> when T: AsRef<U> (which is the reason why we can't have a generic impl<T: ?Sized> AsRef<T> for T).

See also this post on URLO.

Fixing this may be a breaking change and/or require a new edition, I guess.

Update moved up from comment below:

Note that Rc and Arc behave consistent to Cow:

All three are inconsistent with &:

I think that generic(!) smart pointers such as Cow, Rc, and Arc should behave the same as ordinary shared references in regard to AsRef, but they do not. Maybe there is a good reason why this isn't the case. In either case, it's not possible to solve this without breaking a lot of existing code, so I propose:

  • Keeping this issue in mind for future overhaul of std (if ever possible).
  • Documenting this issue properly.

Meta

rustc --version --verbose:

rustc 1.64.0-nightly (495b21669 2022-07-03)
binary: rustc
commit-hash: 495b216696ccbc27c73d6bdc486bf4621d610f4b
commit-date: 2022-07-03
host: x86_64-unknown-freebsd
release: 1.64.0-nightly
LLVM version: 14.0.6

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-bugCategory: This is a bug.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions