Rogue VM implements Rust-like ownership system. And the best description of it is in Rust Book.
We highly recommend you reading ownership chapter in Rust Book even though Rust syntax differs and some of the examples may not be easy to understand. In this chapter we will go through main points anyway.
Each variable has only one owner scope. When owner scope ends - owned variables are dropped.
We've already seen this behavior in expressions chapter. Remember that variable lives as long as its scope? Now is the perfect time to get under the hood and learn why it happens.
Owner is a scope which owns a variable. Variables either can be defined in this scope (e.g. with keyword
let in script) or be passed into scope as argument. Since the only scope in Rogue is function's - there are no other ways to put variables into scope.
Each variable has only one owner, which means that when a variable is passed into function as argument, this function becomes the new owner, and variable no longer owned by the first function. Or you may say that function takes ownership of variable.
Let's look at what happens inside
value() when we pass our value into it:
Of course, a quick workaround is to return a tuple with original variable and additional results (return value would have been
(T, u8)), but Rogue has a better solution for that.
First, you need to understand how Rogue VM works, and what happens when you pass your value into a function. There are two bytecode instructions in VM: RogueLoc and CopyLoc - both of them can be manually used with keywords
When a variable is being passed into another function - it's being moved and RogueLoc OpCode is used. Let's see how our code would look if we've used keyword
This is a valid Rogue code, however, knowing that value will still be moved you don't need to explicitly rogue it. Now when it's clear we can get to copy.
If you need to pass a value to the function (where it's being moved) and save a copy of your variable, you can use keyword
In this example we've passed a copy of variable (hence value)
a into the first call of method
value and saved
a in local scope to use it again in second the call.
By copying value we've duplicated it and increased the memory size of our program, so it can be used - but when you copy huge data it may become pricey in terms of memory. Remember - in blockchains every byte counts and affects price of execution, so using
copy all the time may cost you a lot.
Now you are ready to learn about references which help you avoid unnecessary copying and literally save some money.
Many programming languages have implementation of references (see Wikipedia). Reference is a link to a variable (usually to a section in memory) which you can pass into other parts of the program instead of moving the value.
References (marked with &) allow you to refer to value without taking ownership of it.
Let's modify our example and see how references can be used.
We added ampersand
& to argument type T - and by doing that we've changed argument type from
T to reference to T or simply
Rogue supports two types of references: immutable - defined with
&T) and mutable -
Immutable references allow reading value without changing it. Mutable - the opposite - ability to read and change the value.
Now let's see how to use our upgraded method M.
Use immutable (&) references to read data from structs, use mutable (&mut) to modify them. By using proper type of references you help maintaining security and help reading your methods so the reader will know whether this method changes the value or only reads.
Rogue controls the way you use references and helps you prevent unexpected bullet in your foot. To understand that let's see an example. We'll give you a method and a script and then will comment on what's going on and why.
This code compiles and runs without errors. First, what happens here: we use mutable reference to
A to get mutable reference to its inner struct
B. Then we change
B. Then operation can be repeated.
But what if we've swapped two last expressions and first tried to create a new mutable reference to
A while mutable reference to
B is still alive?
We would have gotten an error:
This code won't compile. Why? Because
&mut A is being borrowed by
&mut B. If we could change
A while having mutable reference to its contents, we'd get into an odd situation where
A can be changed but reference to its contents is still here. Where would
mut_b point to if there was no actual
We come to few conclusions:
- We get a compilation error which means that the Rogue compiler prevents these cases. It's called borrow checking (originally concept from Rust language). Compiler builds a borrow graph and disallows moving borrowed values. This is one of the reasons why Rogue is so safe to use in blockchains.
- You can create reference from reference, so that original reference will be borrowed by the new one. Mutable and immutable can be created from mutable and only immutable from immutable.
- When reference is borrowed it cannot be moved because other values depend on it.
References can be dereferenced to get linked value - to do it use asterisk
When dereferencing you're making a copy. Make sure that value has Copy ability.
Dereference operator does not rogue original value into current scope. It creates a copy of this value instead.
There's a technique in Rogue used to copy inner field of a struct:
*& - dereference a reference to the field. Here's a quick example:
*& (even compiler will advise you to do so) we've copied the inner value of a struct.
Primitive types (due to their simplicity) do not need to be passed as references and copy operation is done instead. Even if you pass them into function by value they will remain in current scope. You can intentionally use
rogue keyword, but since primitives are very small in size copying them may even be cheaper than passing them by reference or even moving.
This script will compile even though we didn't pass
a as a reference. Adding
copy is unnecessary - it's already put there by VM.