Immutable Objects

Create immutable object

To convert an object into an immutable object, call the following function in the transfer module:

public native fun freeze_object<T: key>(obj: T);

This call makes the specified object immutable. This is a non-reversible operation. You should freeze an object only when you are certain that you don't need to mutate it.

Add an entry function to the color_object module to turn an existing (owned) ColorObject into an immutable object:

public entry fun freeze_object(object: ColorObject) {
    transfer::freeze_object(object)
}

In the preceding function, you must already own a ColorObject to pass it in. At the end of this call, this object is frozen and can never be mutated. It is also no longer owned by anyone.

Note that the transfer::freeze_object function requires you to pass the object by value. If the object allowed passing the object by a mutable reference, you could still mutate the object after the freeze_object call. This contradicts the fact that it should have become immutable.

Alternatively, you can also provide an API that creates an immutable object at creation:

public entry fun create_immutable(red: u8, green: u8, blue: u8, ctx: &mut TxContext) {
    let color_object = new(red, green, blue, ctx);
    transfer::freeze_object(color_object)
}

This function creates a new ColorObject and immediately makes it immutable before it has an owner.

Use immutable object

Once an object becomes immutable, the rules of who can use this object in Sui Move calls change:

  1. An immutable object can be passed only as a read-only, immutable reference to Sui Move entry functions as &T.

  2. Anyone can use immutable objects.

In a preceding section, you defined a function that copies the value of one object to another:

public entry fun copy_into(from_object: &ColorObject, into_object: &mut ColorObject);

In this function, anyone can pass an immutable object as the first argument from_object, but not the second argument.

Since immutable objects can never be mutated, there's no data race, even when multiple transactions are using the same immutable object at the same time. Hence, the existence of immutable objects does not pose any requirement on consensus.

Test immutable object

Previously, you used the test_scenario::take_from_sender<T> function to take an object from the global storage that's owned by the sender of the transaction in a unit test. And take_from_sender returns an object by value, which allows you to mutate, delete, or transfer it.

To take an immutable object, use a new function: test_scenario::take_immutable<T>. This is required because you can access immutable objects only through read-only references. The test_scenario runtime keeps track of the usage of this immutable object. If the compiler does not return the object via test_scenario::return_immutable before the start of the next transaction, the test stops.

To see it work in action, (ColorObjectTests::test_immutable):

let sender1 = @0x1;
let scenario_val = test_scenario::begin(sender1);
let scenario = &mut scenario_val;
{
    let ctx = test_scenario::ctx(scenario);
    color_object::create_immutable(255, 0, 255, ctx);
};
test_scenario::next_tx(scenario, sender1);
{
    // take_owned does not work for immutable objects.
    assert!(!test_scenario::has_most_recent_for_sender<ColorObject>(scenario), 0);
};

This test submits a transaction as sender1, which tries to create an immutable object.

The can_take_owned<ColorObject> function no longer returns true, because the object is no longer owned. To take this object:

// Any sender can work.
let sender2 = @0x2;
test_scenario::next_tx(scenario, sender2);
{
    let object = test_scenario::take_immutable<ColorObject>(scenario);
    let (red, green, blue) = color_object::get_color(object);
    assert!(red == 255 && green == 0 && blue == 255, 0);
    test_scenario::return_immutable(object);
};

To show that this object is indeed not owned by anyone, start the next transaction with sender2. Note that it used take_immutable and succeeded. This means that any sender can take an immutable object. To return the object, call a new function: return_immutable.

To examine whether this object is immutable, add a function that tries to mutate a ColorObject:

public entry fun update(
    object: &mut ColorObject,
    red: u8, green: u8, blue: u8,
) {
    object.red = red;
    object.green = green;
    object.blue = blue;
}

Last updated