Description
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
:
impl<T: ?Sized> AsRef<T> for Rc<T>
impl<T: ?Sized> AsRef<T> for Arc<T>
impl<T: ?Sized + ToOwned> AsRef<T> for Cow<'_, T>
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