-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Cow<'_, T> should not implement AsRef<T> but AsRef<U> where T: AsRef<U> #98905
Comments
Also compare @conradludgate's |
@rustbot label T-compiler |
Arguably for your example, all that's missing is PR #73390. See that PR to see how it breaks inference in existing code, which your suggestion would do as well.
Definitely breaking without PR #39397 (which would probably be more disruptive than PR #73390), as |
PR #73390 would be a workaround instead of solving the problem by fixing its cause. Note that it's not possible for external code to provide this workaround for types that are not defined in See this extended example: use std::borrow::Cow;
use std::path::Path;
#[derive(Clone)]
struct CurDir;
impl AsRef<Path> for CurDir {
fn as_ref(&self) -> &Path {
".".as_ref()
}
}
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(CurDir);
func(&CurDir);
func(Cow::Borrowed(&CurDir));
func(s);
func(&s);
func(Cow::Borrowed(s));
} I would also expect this code to compile and run without error, but it doesn't:
It's also (obviously) not possible to add a workaround where needed: use std::borrow::Cow;
use std::path::Path;
#[derive(Clone)]
struct CurDir;
impl AsRef<Path> for CurDir {
fn as_ref(&self) -> &Path {
".".as_ref()
}
}
impl<'a> AsRef<Path> for Cow<'a, CurDir> {
fn as_ref(&self) -> &Path {
self.deref().as_ref()
}
} Which gives (this is not unexpected):
I conclude that PR #73390 is semantically wrong. Instead the cause of the problem should be fixed as proposed in my original post. I understand that this is breaking. However, given the current semantics of
If the cause of the issue cannot be fixed, it should be at least documented as an error in However, leaving |
For a meta discussion on this kind of problems, I opened a new thread on IRLO: Rust's complexity + Rust's stability policy = ? |
That's why I used the phrasing I did. The point is, even fixing the single use-case of the original example was deemed too disruptive, and it isn't even a major breaking change.
Even if there's a way to keep the inference breakage from being too disruptive, it's a major breaking change that can't happen before intersection specialization or Rust 2.0, modulo some other reversion of Rust's backwards compatibility guarantees. That is, the working example you give should indeed and is indeed guaranteed to work, must compile, as per the blanket implementation on I agree the existing medley of traits and implementations are not perfect, but sometimes theoretical purity must bow to practicality. |
A potential migration path might be to provide something like |
I had an error in my original proposed solution. This has been corrected (by editing) as follows: impl<T, U> AsRef<U> for Cow<'_, T>
where
- T: AsRef<U>,
+ T: ?Sized + ToOwned + AsRef<U>,
U: ?Sized,
{
fn as_ref(&self) -> &U {
self.deref().as_ref()
}
} |
Instead of seeing the cause of the problem in
See @QuineDot's post on URLO. Following his reasoning, many occurrences of All this is non-fixable, of course. However, for the sake of completeness, I would like to outline another (hypothetical) non-breaking solution for this issue in the following. I previously proposed:
But instead, -pub fn open<P: AsRef<Path>>(path: P) -> Result<File>`
+pub fn open<P: ?Sized + AsRef2<Path>>(path: &P) -> Result<File> Then Please note that this is not a suggested solution but merely a thought-experiment to better understand the cause of the problem. |
I would like to state that the thesis / topic of this issue report
might require far more discussion, see second (technical) part of my post here. I.e. I'm not sure if my proposed (hypothetical) solutions really are the right way to go (apart from being practical). The underlying problems might be more complicated/tricky. I would like to clarify that I do not propose any immediate action on this issue but wanted to point out inconsistencies, of which some can be seen here:
And here:
|
There would be two potential ways to fix this, of which none are really feasible at this point, I believe: Migration path A:
Note that this would also need to be done with Migration path B:
Choosing migration path B, one of the following mutually exclusive implementations could be chosen:
It is possible that variant B1 would cause practical implications due to lack of transitivity of In no case either of these migration paths appear feasible by now, but the inconsistency remains. Edit: Moved some parts to the OP for a better overview. |
Using |
There is an older issue #45742 (Blanket impl of AsRef for Deref) referring to an old FIXME comment in // As lifts over &
/* … */
impl<T: ?Sized, U: ?Sized> const AsRef<U> for &T
where
T: ~const AsRef<U>,
{
/* … */
}
// As lifts over &mut
/* … */
impl<T: ?Sized, U: ?Sized> const AsRef<U> for &mut T
where
T: ~const AsRef<U>,
{
/* … */
}
// FIXME (#45742): replace the above impls for &/&mut with the following more general one:
// // As lifts over Deref
// impl<D: ?Sized + Deref<Target: AsRef<U>>, U: ?Sized> AsRef<U> for D {
// fn as_ref(&self) -> &U {
// self.deref().as_ref()
// }
// } Since this issue (#98905) is a special case (only addressing Documentation should still be improved on that matter. I started a discussion regarding the semantics of |
…riplett docs: Improve AsRef / AsMut docs on blanket impls There are several issues with the current state of `AsRef` and `AsMut` as [discussed here on IRLO](https://internals.rust-lang.org/t/semantics-of-asref/17016). See also rust-lang#39397, rust-lang#45742, rust-lang#73390, rust-lang#98905, and the FIXMEs [here](https://github.com/rust-lang/rust/blob/1.62.0/library/core/src/convert/mod.rs#L509-L515) and [here](https://github.com/rust-lang/rust/blob/1.62.0/library/core/src/convert/mod.rs#L530-L536). These issues are difficult to fix. This PR aims to update the documentation to better reflect the status-quo and to give advice on how `AsRef` and `AsMut` should be used. In particular: - Explicitly mention that `AsRef` and `AsMut` do not auto-dereference generally for all dereferencable types (but only if inner type is a shared and/or mutable reference) - Give advice to not use `AsRef` or `AsMut` for the sole purpose of dereferencing - Suggest providing a transitive `AsRef` or `AsMut` implementation for types which implement `Deref` - Add new section "Reflexivity" in documentation comments for `AsRef` and `AsMut` - Provide better example for `AsMut` - Added heading "Relation to `Borrow`" in `AsRef`'s docs to improve structure
…riplett docs: Improve AsRef / AsMut docs on blanket impls There are several issues with the current state of `AsRef` and `AsMut` as [discussed here on IRLO](https://internals.rust-lang.org/t/semantics-of-asref/17016). See also rust-lang#39397, rust-lang#45742, rust-lang#73390, rust-lang#98905, and the FIXMEs [here](https://github.com/rust-lang/rust/blob/1.62.0/library/core/src/convert/mod.rs#L509-L515) and [here](https://github.com/rust-lang/rust/blob/1.62.0/library/core/src/convert/mod.rs#L530-L536). These issues are difficult to fix. This PR aims to update the documentation to better reflect the status-quo and to give advice on how `AsRef` and `AsMut` should be used. In particular: - Explicitly mention that `AsRef` and `AsMut` do not auto-dereference generally for all dereferencable types (but only if inner type is a shared and/or mutable reference) - Give advice to not use `AsRef` or `AsMut` for the sole purpose of dereferencing - Suggest providing a transitive `AsRef` or `AsMut` implementation for types which implement `Deref` - Add new section "Reflexivity" in documentation comments for `AsRef` and `AsMut` - Provide better example for `AsMut` - Added heading "Relation to `Borrow`" in `AsRef`'s docs to improve structure
…riplett docs: Improve AsRef / AsMut docs on blanket impls There are several issues with the current state of `AsRef` and `AsMut` as [discussed here on IRLO](https://internals.rust-lang.org/t/semantics-of-asref/17016). See also rust-lang#39397, rust-lang#45742, rust-lang#73390, rust-lang#98905, and the FIXMEs [here](https://github.com/rust-lang/rust/blob/1.62.0/library/core/src/convert/mod.rs#L509-L515) and [here](https://github.com/rust-lang/rust/blob/1.62.0/library/core/src/convert/mod.rs#L530-L536). These issues are difficult to fix. This PR aims to update the documentation to better reflect the status-quo and to give advice on how `AsRef` and `AsMut` should be used. In particular: - Explicitly mention that `AsRef` and `AsMut` do not auto-dereference generally for all dereferencable types (but only if inner type is a shared and/or mutable reference) - Give advice to not use `AsRef` or `AsMut` for the sole purpose of dereferencing - Suggest providing a transitive `AsRef` or `AsMut` implementation for types which implement `Deref` - Add new section "Reflexivity" in documentation comments for `AsRef` and `AsMut` - Provide better example for `AsMut` - Added heading "Relation to `Borrow`" in `AsRef`'s docs to improve structure
I tried this code:
I expected to see this happen: Code compiles and runs without error.
Instead, this happened:
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 thatshould actually be:
That is because
.as_ref()
can't generally be used to go fromT
to&T
. It doesn't need to go fromCow<'_, T>
to&T
either. We have.borrow()
for that, because there is animpl<T: ?Sized> Borrow<T> for T
instd
.Instead,
Cow
should be transparent in regard toAsRef
, i.e. if a typeT
implementsAsRef<U>
, thenCow<'_, T>
should implementAsRef<U>
too. Compare&T
, which implementsAsRef<U>
whenT: AsRef<U>
(which is the reason why we can't have a genericimpl<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
andArc
behave consistent toCow
: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
&
:impl<T: ?Sized + AsRef<U>, U: ?Sized> AsRef<U> for &T
I think that generic(!) smart pointers such as
Cow
,Rc
, andArc
should behave the same as ordinary shared references in regard toAsRef
, 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:Meta
rustc --version --verbose
:The text was updated successfully, but these errors were encountered: