Skip to content

Commit 7d0db1c

Browse files
committed
Arbitrary self types v2: shadowing semver break.
This is a pre-existing risk of semver breakage for types implementing Deref<Target=T>. It's made more apparent by the arbitrary self types v2 change, and will now generate errors under some circumstances. This PR documents the existing risk and the new ways it can be triggered. If it's seen as desirable to document the existing risk before Arbitrary Self Types v2 is stabilized, we can split this commit up into two. One of the two code examples here is marked "skip" because it depends on the nightly feature which is under discussion for stabilization. Part of rust-lang/rust#44874 Stabilization PR rust-lang/rust#135881
1 parent 730d997 commit 7d0db1c

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

src/doc/src/reference/semver.md

+132
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ considered incompatible.
8888
* [Minor: generalizing a function to use generics (supporting original type)](#fn-generalize-compatible)
8989
* [Major: generalizing a function to use generics with type mismatch](#fn-generalize-mismatch)
9090
* [Minor: making an `unsafe` function safe](#fn-unsafe-safe)
91+
* [Major: adding a potentially shadowing method](#fn-add-potentially-shadowing-method)
9192
* Attributes
9293
* [Major: switching from `no_std` support to requiring `std`](#attr-no-std-to-std)
9394
* [Major: adding `non_exhaustive` to an existing enum, variant, or struct with no private fields](#attr-adding-non-exhaustive)
@@ -1883,6 +1884,137 @@ Making a previously `unsafe` associated function or method on structs / enums
18831884
safe is also a minor change, while the same is not true for associated
18841885
function on traits (see [any change to trait item signatures](#trait-item-signature)).
18851886

1887+
### Major: add a potentially shadowing method {#fn-add-potentially-shadowing-method}
1888+
1889+
If you have a type which implements `Deref<Target=T>`, you must not add methods
1890+
which may "shadow" methods in `T`. This can lead to unexpected changes in
1891+
program behavior.
1892+
1893+
```rust,ignore,run-fail
1894+
// MAJOR CHANGE
1895+
1896+
///////////////////////////////////////////////////////////
1897+
// Before
1898+
#[derive(Clone, Copy)]
1899+
pub struct MySmartPtr<T>(pub T);
1900+
1901+
impl<T> core::ops::Deref for MySmartPtr<T> {
1902+
type Target = T;
1903+
fn deref(&self) -> &Self::Target {
1904+
&self.0
1905+
}
1906+
}
1907+
1908+
///////////////////////////////////////////////////////////
1909+
// After
1910+
#[derive(Clone, Copy)]
1911+
pub struct MySmartPtr<T>(pub T);
1912+
1913+
impl<T> core::ops::Deref for MySmartPtr<T> {
1914+
type Target = T;
1915+
fn deref(&self) -> &Self::Target {
1916+
&self.0
1917+
}
1918+
}
1919+
1920+
impl<T> MySmartPtr<T> {
1921+
pub fn method(self) -> usize {
1922+
2
1923+
}
1924+
}
1925+
1926+
///////////////////////////////////////////////////////////
1927+
// Example usage that will break.
1928+
use updated_crate::MySmartPtr;
1929+
1930+
struct SomeStruct;
1931+
1932+
impl SomeStruct {
1933+
fn method(&self) -> usize {
1934+
1
1935+
}
1936+
}
1937+
1938+
fn main() {
1939+
let mut ptr = MySmartPtr(SomeStruct);
1940+
assert_eq!(ptr.method(), 1);
1941+
}
1942+
```
1943+
1944+
Note that the shadowing and shadowed methods receive `self`
1945+
slightly differently: `&self` and `&mut self`.
1946+
That's because Rust searches for methods first by value, then by `&`, then
1947+
by `&mut T`, then by `*const T`. Rust stops the search
1948+
when it encounters a valid method, and so methods later in this order may
1949+
be shadowed by methods encountered earlier.
1950+
1951+
This is only a compatibility risk if the `Deref` target is
1952+
beyond your control. If your type implements `Deref` to another type where
1953+
you can fix the available methods, you can ensure no shadowing
1954+
occurs. An example is that `PathBuf` implements
1955+
`Deref<Target=Path>`.
1956+
1957+
For types which do implement `Deref` with an arbitrary target,
1958+
it's bad practice to add methods: add associated functions instead. This is
1959+
the pattern used by Rust's standard library smart pointer types, such as
1960+
`Box`, `Rc` and `Arc`.
1961+
1962+
Similar shadowing risks occur for a type implementing
1963+
`Receiver<Target=T>`. If you have a type which implements either
1964+
`Receiver<Target=T>` or `Deref<Target=T>` it may be used as a method receiver
1965+
by `T`'s methods. If your type then adds a method, you may shadow methods in
1966+
`T`. For instance:
1967+
1968+
```rust,ignore,skip
1969+
// MAJOR CHANGE
1970+
1971+
///////////////////////////////////////////////////////////
1972+
// Before
1973+
#![feature(arbitrary_self_types)]
1974+
pub struct MySmartPtr<T>(pub T);
1975+
1976+
impl<T> core::ops::Receiver for MySmartPtr<T> {
1977+
// or Deref
1978+
type Target = T;
1979+
}
1980+
1981+
///////////////////////////////////////////////////////////
1982+
// After
1983+
#![feature(arbitrary_self_types)]
1984+
pub struct MySmartPtr<T>(pub T);
1985+
1986+
impl<T> core::ops::Receiver for MySmartPtr<T> {
1987+
// or Deref
1988+
type Target = T;
1989+
}
1990+
1991+
impl<T> MySmartPtr<T> {
1992+
pub fn method(self) {}
1993+
}
1994+
1995+
///////////////////////////////////////////////////////////
1996+
// Example usage that will break.
1997+
#![feature(arbitrary_self_types)]
1998+
use updated_crate::MySmartPtr;
1999+
2000+
struct SomeStruct;
2001+
2002+
impl SomeStruct {
2003+
fn method(self: &MySmartPtr<Self>) {}
2004+
}
2005+
2006+
fn main() {
2007+
let ptr = MySmartPtr(SomeStruct);
2008+
ptr.method(); // Error: multiple applicable items in scope
2009+
}
2010+
```
2011+
2012+
When types like this are being used as method receivers, Rust endeavours to
2013+
do additional searches and present errors in simple cases, e.g. shadowing of
2014+
`&self` by `self` with inherent methods. This is better than invisible
2015+
behavior changes - but either way it's a compatibility break. Avoid adding
2016+
methods if you implement `Deref` or `Receiver` to an arbitrary target.
2017+
18862018
### Major: switching from `no_std` support to requiring `std` {#attr-no-std-to-std}
18872019

18882020
If your library specifically supports a [`no_std`] environment, it is a

0 commit comments

Comments
 (0)