Take
let s: Box<str> = " Hello ".into();
Now, what does trimming do?
let trimmed = s.trim(); /* str::trim(&*s) */
Now, all is good, except for one thing: you cannot move s
while trimmed
exists!! The reason for that is that you shouldn't be able to move the characters around /
free
the allocated characters while trimmed
exists: so trimmed
cannot outlive s
, and more generally, the contents s
points to should not be mutated while trimmed
exists.
This is why we call this a borrow: for the time trimmed
lives (with NLL, the time trimmed
is used), a duration that we can call / denote 'a
, that is, the lifetime 'a
, s
cannot access the contents it points to since they have been borrowed by trimmed
.
Now, the problem is, that Rust cannot make the difference between s
itself (i.e., the ptr, len
pair) and the "contents s
points to". So, while it is safe to move the ptr, len
pair around, Rust does not know that, and forbids it (example).
The solution most people use then is to copy the trimmed contents elsewhere, so that they are not tied to s
anymore:
let trimmed: String = s.trim().to_string(); // or
let trimmed: String = s.trim().to_owned(); // or
let trimmed: Box<str> = s.trim().into();
-
(the only difference between a String
and a Box<str>
is a third field, capacity: usize
, which does not really play any role in my example, so I will stick to Box<str>
for the sake of simplicity)
-
Playground
As you can see, we no longer borrow from s
, at the cost of duplicating / copying the "Hello"
string elsewhere in the heap: we have reallocated "Hello"
Doing this just to satisfy the borrow checker is quite saddening.
The solution of owning-ref
or a custom implementation based on Pin
<Box<..>>
, is that, since moving s
does not move the contents it points to, and by forbidding mutation of the pointee (you will note that there is no DerefMut
what so ever for OwningRef<_>
), if we manage to ensure that the reference (trimmed
) does not outlive s
, then all should be fine (Rust can still not know that, so unsafe
is required). And this is achieved by stitching the reference and s
together in a single struct: OwningRef
.
It starts off with a dummy reference to the whole contents (e.g., " Hello "
with the trailing spaces), in which case it "wastes" a little bit more stack memory than a simple Box<str>
, but then, when using the .map()
method, we can manipulate that reference and "shrink" it at will. And the magic is that whenever we want to access / read the contents the reference points to, Rust uses the Deref
trait, which has been overloaded to use the shrunk ref instead of the owning variable.