Skip to content

Commit 77f5ca5

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. Part of rust-lang/rust#44874 Stabilization PR rust-lang/rust#135881
1 parent cecde95 commit 77f5ca5

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

src/doc/src/reference/semver.md

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

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

0 commit comments

Comments
 (0)