Currently circuit types include member variables and member functions. The latter may be static or not.
There are plans to eliminate the static/non-static distinction and have just member functions, with three possibilities:
- Functions without a
self
parameter. - Functions with a
self
parameter (as first parameter). - Functions with a
mut self
parameter (as first parameter).
Here we assume we are already in this situation.
Let C
be a circuit type, whose three kinds of member functions are discussed below.
Functions without a self
parameter (#1 above) are todayâs static functions. They are not very different from top-level functions, except that (i) their bodies may use Self
as a synonym of C
and (ii) their calls must be prefixed by C::
. If Leo is extended with access control on circuit members (e.g. public or not), then another difference with top-level functions could be the ability to access non-public members of C
. However, these differences with top-level functions are not profound. Like top-level functions, circuit functions without self
can be described purely functionally (in the sense of pure functional programming, i.e. without side effects).
Functions with a self
parameter (#2 above) are not allowed to modify self
, i.e. the circuit value they are called on. They are not very different from top-level functions with a C
as first parameter, except that (i) their bodies may use Self
as a synonym for C
, (ii) the name of the first parameter is self
instead of an identifier, and (iii) the call is written c.f(...)
instead of f(c,...)
. Another possible future difference is the ability to access non-public members, as noted for functions of kind #1 above. If f
is a function of this kind, a call c.f(...)
, where c
is any expression of type C
, is much like a call f(c,...)
if f
were a top-level function. The semantics of this call can be described as evaluating the expression c
to a value of type C
, evaluating any additional argument expressions of the call, and then executing f
on all these values, obtaining a result value. This can be described purely functionally, as with kind #1 above.
Functions with a mut self
parameter (#3 above) are allowed to modify self
, and the modifications are visible to the caller â this is what the current examples and use cases show. This cannot be described purely functionally: it is an imperative feature. (Mathematically, we can still use functions to describe imperative semantics, but the functions must take âstateâ as additional input and output.) In effect, inside these functions, self
is not really a circuit value, but a reference to an âexternalâ circuit value: this way, any assignment to its member variables happens on the external circuit value, not any âinternalâ copy of it. (Alternatively, we could say that self
is a copied circuit value inside the function, and that its final value overwrites the external value when the function returns. But the substance does not change, it is just a different way to describe what happens.)
The fact that a function f
of kind #3 is passed self
by reference and not by value constrains the form of the circuit expressions on which the function may be called. That is, c.f(...)
cannot be allowed for any expression c
of type C
, but only for expressions that denote a âlocationâ, i.e. what the current Pest grammar calls âassigneesâ. (In the C language, these would be called lvalues, for âleft valuesâ, i.e. expressions that may appear on the left of an assignment operator. In fact, the âvalueâ in âlvalueâ is a bit of a misnomer in the C language terminology; it should perhaps be âlexpressionâ.) In Leo, a call c.f(...)
, besides returning a result value like all other functions, behaves like an assignment to c
, so c
must be something that can be on the left side of an assignment.
This, i.e. mut self
, is the only case in which a Leo function argument is passed by reference to a function. In all other cases, it is passed by value, i.e. a copy of the value is passed â at least conceptually, but a compiler can transparently optimize that to pass a reference just to avoid the copy, if it can establish that it is safe to do so. If Leo is extended with the ability to pass other arguments by reference (e.g. to call a function to swap the contents of two integer variables), then mut self
could be described not as a special case, but as one form of pass-by-reference.