Immutable references - `let ref`
Different to Rust, Blech takes references and de-references whenever it is necessary.
Althoug Blech semantically uses pass-by-reference it is possible to pass a complete right-hand-side expression for input parameters.
Let’s take our example from the introduction
var i: int32 var j: int32 outIsInPlus1(j + 1)(i)
This programm is valid. Semantically Blech creates a auxiliary memory location from which the read-only reference can be taken. For a function on simple types the generated code can be optimized and the parameter can be passed by-value.
This is not true for an activity.
activity outAlwaysIsInPlus1 (a: int32) (b: int32) repeat b = 1 b = a + b await true b = 1 b = a + b await true end end
var i: int32 var j: int32 run outAlwaysIsInPlus1(j + 1)(i)
In every step the activity gets
j+1 as its input.
For the code generation the auxiliary memory location is necessary.
We imagine the new value is pumped into the activity in every step.
Immutable borrowing in sub-expressions
Due to a causality error the Blech compiler rejects the following program.
var i: int32 run outAlwaysIsInPlus1(i + 1)(i)
Furthermore the compiler rejects a similar function call
var i: int32 outIsInPlus1(i + 1)(i)
This is different to Rust, because Rust cannot take immutable (read-only) references to right-hand side expressions.
The borrow checker needs to check the occurence of read-only borrowing in right-hand side expressions.
Pure functions only have input parameters and a return value.
For pure functions pass-by-reference and pass-by-value are semantically the same, because nothing at the call side gets changed.
All build-in operators in Blech are pure functions.
Since we can pass a right-hand-side expression as an input parameter we should also be able to take a reference to a right-hand-side expression in sequential code.
This makes sense in an activity and is called
bind in the language Quartz.
activity a (i: int32) (j: int32) let ref double = i * 2 repeat j = double await true end end
In order to take a reference to a right-hand-side expression the compiler would generate an additional memory location.
This has roughly the following semantics:
activity a (i: int32) (j: int32) var aux_double: int32 // not subject to borrow checking let ref double = internal_aux_double cobegin weak repeat aux_double = i * 2 await true end with repeat j = double await true end end end
For the declaration
let ref double = i * 2 we need to apply the borrowing rules for the sub-expression
For example in the following code would then be rejected.
var i: int32 var z: int32 let ref double = i * 2 // immutable borrow of i repeat z = double await true until z > 1000 end i = i + 1 // i is no longer accessible here
The read-only reference
double immutable borrows
i, which locks
i for further usage.
As usual a separate scope can solve the problem:
var i: int32 var z: int32 do let ref double = i * 2 repeat z = double await true until z > 1000 end i = i + 1
For purely sequential code that is not re-entered on every tick, the contents of the auxiliary memory location gets only assigned once.
It not possible to take a reference for a right-hand-side expression as an output or a
var ref declaration.