Non-copyable types
A hotel key stands for a room. If keys could be copied, two of them would each be handed back at checkout — the destructor would run twice for one room. For a type like this, the compiler's automatic copy is wrong, and Rux lets the type turn it off.
Once copying is off, the only way to hand a key on is to move it. One key, one owner, one destructor call — and the compiler checks all three.
Turning copying off
The copy operator is =. Declaring it inside extend with no body says "this type has no copy":
struct RoomKey {
room: int32;
}
extend RoomKey {
// No body: copying is prohibited.
func =(self: &var RoomKey, other: &RoomKey);
func ~RoomKey(self: &var RoomKey) {
PrintLine(" key {} handed back", self.room);
}
}
The signature is the one a real copy would have — self is the new value to fill in, other the value being copied — but with a ; where the body would be. The Custom copy lesson gives it a body.
Moving instead
From then on every use that would copy a key is an error, and the diagnostic shows the move to write instead:
let spare = key;
error: move-only value 'key' requires an explicit '<-' in initialization
note: plain by-value use copies its source, but 'RoomKey' prohibits copying
help: write 'let destination <- key' to transfer ownership
With the arrow, the key changes hands and key is finished:
let friend <- key;
PrintLine(" friend holds key {}", friend.room);
Passing by value is a copy too, so an argument needs the arrow as well. CheckOut takes ownership of the key, so the key's life ends when the function returns:
func CheckOut(key: RoomKey) {
PrintLine(" checking out of room {}", key.room);
}
CheckOut(<-friend);
PrintLine(" done");
That is why the output shows "key 12 handed back" between "checking out of room 12" and "done": the destructor runs as CheckOut returns, not at the end of Main.
Fresh keys and borrowed keys
Two things still work without an arrow. A value made on the spot has no old owner, so CheckIn returns its new key with a plain return:
func CheckIn(room: int32) -> RoomKey {
PrintLine(" key {} issued", room);
return RoomKey { room: room };
}
And a borrow is not a copy. A function that only needs to look at a key takes &RoomKey, and the caller keeps the key.
flowchart LR
k(["key: RoomKey<br/>no copy"]) -- "let spare = key" --> x["error: requires<br/>an explicit '<-'"]
k -- "let friend <- key" --> f["friend owns the key<br/>key is finished"]
k -- "Peek(key) with key: &RoomKey" --> b["borrowed for the call<br/>key still owns it"]Everywhere a copy would happen
Every by-value use of a named key needs the arrow. The compiler names the place in each error:
| Where | Refused | Written with a move |
|---|---|---|
| A binding | let spare = key; | let spare <- key; |
| An argument | CheckOut(key) | CheckOut(<-key) |
| A return | return key; | return <-key; |
| A struct field | Booking { key: key } | Booking { key: <-key } |
| An assignment | key = other; | key <- other; |
The prohibition also spreads outwards. A struct with a RoomKey field cannot be copied either — copying it would copy the key — so the compiler treats the whole struct as move-only.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A hotel key stands for a room. If keys could be copied, two of them would each be handed back
// at checkout: the destructor would run twice for one room. For a type like this the compiler's
// automatic copy is wrong, and Rux lets the type turn it off.
//
// The copy operator is `=`. Declaring it with no body says "this type has no copy":
//
// func =(self: &var RoomKey, other: &RoomKey);
//
// From then on every use that would copy a key is an error, and the only way to hand one on is
// to move it with `<-`. One key, one owner, one destructor call.
import Io::PrintLine;
struct RoomKey {
room: int32;
}
extend RoomKey {
// No body: copying is prohibited.
func =(self: &var RoomKey, other: &RoomKey);
func ~RoomKey(self: &var RoomKey) {
PrintLine(" key {} handed back", self.room);
}
}
func CheckIn(room: int32) -> RoomKey {
PrintLine(" key {} issued", room);
return RoomKey { room: room };
}
// Takes ownership of the key, so the key's life ends when this function returns.
func CheckOut(key: RoomKey) {
PrintLine(" checking out of room {}", key.room);
}
func Main() -> int {
PrintLine("check in:");
let key = CheckIn(12);
// A copy is refused, and the diagnostic shows the move to write instead:
//
// let spare = key;
//
// error: move-only value 'key' requires an explicit '<-' in initialization
// note: plain by-value use copies its source, but 'RoomKey' prohibits copying
// help: write 'let destination <- key' to transfer ownership
PrintLine("give the key to a friend:");
let friend <- key;
PrintLine(" friend holds key {}", friend.room);
// Passing by value is a copy too, so the argument needs the arrow as well. Without it:
// error: move-only value 'friend' requires an explicit '<-' in argument
PrintLine("check out:");
CheckOut(<-friend);
PrintLine(" done");
return 0;
}
Run it
cd Examples/Ownership/NoCopy
rux run
check in:
key 12 issued
give the key to a friend:
friend holds key 12
check out:
checking out of room 12
key 12 handed back
done
Common mistakes
let spare = key; fails with error: move-only value 'key' requires an explicit '<-' in initialization, and CheckOut(friend) with error: move-only value 'friend' requires an explicit '<-' in argument. Both notes say the same thing: plain by-value use copies its source, and this type prohibits copying.return.A function that builds a key in a local and ends with
return key; fails with error: move-only value 'key' requires an explicit '<-' in return. Write return <-key;. Only a value made on the spot, like return RoomKey { room: room };, needs no arrow.=.With two
var keys, key = other; fails with error: copying type 'RoomKey' is prohibited. Assignment copies too. Write key <- other;, which hands back the old key first and then moves other in.Try it yourself
- Write
func Peek(key: &RoomKey)that prints the room number, and call it twice onkeybefore giving it to the friend. Why doesPeekneed no arrow? - Write
func Spare(room: int32) -> RoomKeythat stores a new key in aletbefore returning it. Whichreturndoes it need? - Make two
varkeys and replace the first withkey <- other;. When is each key handed back? - Declare
struct Booking { guest: char8[..]; key: RoomKey; }, build one, and try to copy it with=. What does the error say aboutBooking?
Learn more
- Move — the arrow in all three places
- Custom copy — giving
=a body instead - Partial move — taking a struct of move-only fields apart