Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Update mutability docs #4298

Merged
merged 2 commits into from
Feb 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 55 additions & 27 deletions docs/docs/noir/concepts/mutability.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: Mutability
description:
Learn about mutable variables, constants, and globals in Noir programming language. Discover how
Learn about mutable variables in Noir. Discover how
to declare, modify, and use them in your programs.
keywords: [noir programming language, mutability in noir, mutable variables, constants, globals]
keywords: [noir programming language, mutability in noir, mutable variables]
sidebar_position: 8
---

Expand Down Expand Up @@ -49,45 +49,73 @@ fn helper(mut x: i32) {
}
```

## Comptime Values
## Non-local mutability

:::warning
Non-local mutability can be achieved through the mutable reference type `&mut T`:

The 'comptime' keyword was removed in version 0.10. The comptime keyword and syntax are currently still kept and parsed for backwards compatibility, but are now deprecated and will issue a warning when used. `comptime` has been removed because it is no longer needed for accessing arrays.

:::
```rust
fn set_to_zero(x: &mut Field) {
*x = 0;
}

## Globals
fn main() {
let mut y = 42;
set_to_zero(&mut y);
assert(*y == 0);
}
```

Noir also supports global variables. However, they must be known at compile-time. The global type can also be inferred by the compiler entirely. Globals can also be used to specify array
annotations for function parameters and can be imported from submodules.
When creating a mutable reference, the original variable being referred to (`y` in this
example) must also be mutable. Since mutable references are a reference type, they must
be explicitly dereferenced via `*` to retrieve the underlying value. Note that this yields
a copy of the value, so mutating this copy will not change the original value behind the
reference:

```rust
global N: Field = 5; // Same as `global N: Field = 5`
fn main() {
let mut x = 1;
let x_ref = &mut x;

let mut y = *x_ref;
let y_ref = &mut y;

fn main(x : Field, y : [Field; N]) {
let res = x * N;
x = 2;
*x_ref = 3;

assert(res == y[0]);
y = 4;
*y_ref = 5;

let res2 = x * my_submodule::N;
assert(res != res2);
assert(x == 3);
assert(*x_ref == 3);
assert(y == 5);
assert(*y_ref == 5);
}
```

mod my_submodule {
use dep::std;
Note that types in Noir are actually deeply immutable so the copy that occurs when
dereferencing is only a conceptual copy - no additional constraints will occur.

global N: Field = 10;
Mutable references can also be stored within structs. Note that there is also
no lifetime parameter on these unlike rust. This is because the allocated memory
always lasts the entire program - as if it were an array of one element.

fn my_helper() -> Field {
let x = N;
x
```rust
struct Foo {
x: &mut Field
}

impl Foo {
fn incr(mut self) {
*self.x += 1;
}
}
```

## Why only local mutability?
fn main() {
let foo = Foo { x: &mut 0 };
foo.incr();
assert(*foo.x == 1);
}
```

Witnesses in a proving system are immutable in nature. Noir aims to _closely_ mirror this setting
without applying additional overhead to the user. Modeling a mutable reference is not as
straightforward as on conventional architectures and would incur some possibly unexpected overhead.
In general, you should avoid non-local & shared mutability unless it is needed. Sticking
to only local mutability will improve readability and potentially improve compiler optimizations as well.
Loading