Skip to content

Commit 1f44f8e

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 1f44f8e

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,136 @@ 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 `&self`.
1946+
That's because Rust [searches for methods] first by value, then by `&`, then
1947+
by `&mut T`. Rust stops the search when it encounters a valid method, and
1948+
so methods later in this order may be shadowed by methods encountered earlier.
1949+
1950+
This is only a compatibility risk if the `Deref` target is
1951+
beyond your control. If your type implements `Deref` to another type where
1952+
you can fix the available methods, you can ensure no shadowing
1953+
occurs. An example is that `PathBuf` implements
1954+
`Deref<Target=Path>`.
1955+
1956+
For types which do implement `Deref` with an arbitrary target,
1957+
it's bad practice to add methods: add associated functions instead. This is
1958+
the pattern used by Rust's standard library smart pointer types, such as
1959+
`Box`, `Rc` and `Arc`.
1960+
1961+
Similar shadowing risks occur for a type implementing
1962+
`Receiver<Target=T>`. If you have a type which implements either
1963+
`Receiver<Target=T>` or `Deref<Target=T>` it may be used as a method receiver
1964+
by `T`'s methods. If your type then adds a method, you may shadow methods in
1965+
`T`. For instance:
1966+
1967+
```rust,ignore,skip
1968+
// MAJOR CHANGE
1969+
1970+
///////////////////////////////////////////////////////////
1971+
// Before
1972+
#![feature(arbitrary_self_types)]
1973+
pub struct MySmartPtr<T>(pub T);
1974+
1975+
impl<T> core::ops::Receiver for MySmartPtr<T> {
1976+
// or Deref
1977+
type Target = T;
1978+
}
1979+
1980+
///////////////////////////////////////////////////////////
1981+
// After
1982+
#![feature(arbitrary_self_types)]
1983+
pub struct MySmartPtr<T>(pub T);
1984+
1985+
impl<T> core::ops::Receiver for MySmartPtr<T> {
1986+
// or Deref
1987+
type Target = T;
1988+
}
1989+
1990+
impl<T> MySmartPtr<T> {
1991+
pub fn method(self) {}
1992+
}
1993+
1994+
///////////////////////////////////////////////////////////
1995+
// Example usage that will break.
1996+
#![feature(arbitrary_self_types)]
1997+
use updated_crate::MySmartPtr;
1998+
1999+
struct SomeStruct;
2000+
2001+
impl SomeStruct {
2002+
fn method(self: &MySmartPtr<Self>) {}
2003+
}
2004+
2005+
fn main() {
2006+
let ptr = MySmartPtr(SomeStruct);
2007+
ptr.method(); // Error: multiple applicable items in scope
2008+
}
2009+
```
2010+
2011+
When types like this are being used as method receivers, Rust endeavours to
2012+
do additional searches and present errors in simple cases, e.g. shadowing of
2013+
`&self` by `self` with inherent methods. This is better than invisible
2014+
behavior changes - but either way it's a compatibility break. Avoid adding
2015+
methods if you implement `Deref` or `Receiver` to an arbitrary target.
2016+
18862017
### Major: switching from `no_std` support to requiring `std` {#attr-no-std-to-std}
18872018

18882019
If your library specifically supports a [`no_std`] environment, it is a
@@ -2317,3 +2448,4 @@ document what your commitments are.
23172448
[wildcard patterns]: ../../reference/patterns.html#wildcard-pattern
23182449
[unused_unsafe]: ../../rustc/lints/listing/warn-by-default.html#unused-unsafe
23192450
[msrv-is-minor]: https://github.com/rust-lang/api-guidelines/discussions/231
2451+
[searches for methods]: ../../reference/expressions/method-call-expr.html#method-call-expressions

0 commit comments

Comments
 (0)