Semantic of smart pointers

"Smart pointer" is an individual concept in The Rust Programming Language book. However, I didn't find a semantic-level definition of what "smart pointer" is. Should there be some traits in core/std to provide this semantic?

This question arises from another question (How to constrain types where `AsRef` and `AsMut` point to the same data? - help - The Rust Programming Language Forum) I posted in the users forum:

When a function requires a smart pointer over a specific type, how to express this constraint?

I guess this scenario is common? In my code, I often require a smart pointer to [u8] buffer, which enables me to access and modify the buffer's data (without appending or removing the buffer). Discussion of the problem: whether the code patterns that require a smart pointer is common, is also very welcome :slight_smile:

I was thinking of an unsafe trait (also suggested by others in the users forum post):

/// # SAFETY
///
/// Implementors should make sure the .as_ref() and .as_mut() returns the same data
unsafe trait AsData<T>: AsRef<T> + AsMut<T> {}

However, the correctness requirement in "SAFETY" section is still too weak to describe a smart pointer (which is also pointed out in the users forum post). The requirement only states that as_ref and as_mut return the same data, but there is no requirement of temporal consistency: the pointed data address should never change unless we deliberately replace it through as_mut. This is to prevent the implementors to randomly return different pointers in each invocations. I thought this is something like the DerefPure trait. This requirement is hard to express correctly, since what will happen if the smart pointer is a Vec and it reallocates?

So I suggest we shall discuss the semantic of smart pointers in Rust, and is it desirable to add some related trait to the core/std to express the semantic in type system?

If it’s just for correctness, then you can just document “hey, if you use pathological AsRef/AsMut implementations, this code could panic or be incorrect.” If it’s for the soundness of unsafe code, then I think that needs are commonly fairly specific to the unsafe code. A generic “smart pointer” can just do so many things.

Cow is considered to be a smart pointer, for example, and while std’s Cow doesn’t implement AsMut, it doesn’t seem entirely unreasonable that a non-std Cow-like type could be copy-on-write while implementing both AsRef and AsMut. I don’t think that would be a good idea unless the copy-on-write can be performed cheaply and infallibly (so, no allocating or locking, which is quite restrictive for copy-on-write), but someone might complain if The Generic Definition of Smart Pointer (TM) doesn’t allow for copy-on-write in its equivalent of as_mut/borrow_mut/deref_mut.

It’d be better to make or use an unsafe trait with exactly what you need. Personally, I need a generic growable buffer more frequently than a generic fixed-size buffer, and abstracting the functionality for growing necessitates a trait anyway.

While I’ve been assuming that there’s some reason you can’t just use &mut [u8] (or raw pointers), I suppose it’s worth saying at least once that it’d be even better if your code didn’t need to own the buffer.

1 Like

Thank you for your reply!

Yes, I care about unsafe code.

Yes, and that is exactly why I thought defining a smart pointer is hard. Rc, for another example, cannot implement AsMut.

Yes, &mut [u8] can work.

Then if the struct containing the reference needs to be moved around, it will be very hard for the library user to maintain the code, I guess.