ch31.md 5.6 KB

Chapter 31. View Types

         View types are more effective with "strict funcs".

A view type is a type that is or contains one of the following types: • lent T (view into T) • openArrayT For example: type

View1 = openArray[byte]
View2 = lent string
View3 = Table[openArray[char], int]

Exceptions to this rule are types constructed via ptr or proc. For example, the following types are not view types: type

NotView1 = proc (x: openArray[int])
NotView2 = ptr openArray[char]
NotView3 = ptr array[4, lent int]

The mutability aspect of a view type is not part of the type but part of the locations it’s derived from. A view is a symbol (a let, var, const, etc.) that has a view type. Nim allows view types to be used as local variables. In the current implementation this feature needs to be enabled via {.experimental: "views".}.

                                                                       219

A local variable of a view type borrows from the locations and it is statically enforced that the view does not outlive the location it was borrowed from. For example: {.experimental: "views".} proc take(a: openArray[int]) =

echo a.len

proc main(s: seq[int]) =

var x: openArray[int] = s # 'x' is a view into 's'
# it is checked that 'x' does not outlive 's' and
# that 's' is not mutated.
for i in 0 .. high(x):
  echo x[i]
take(x)
take(x.toOpenArray(0, 1)) # slicing remains possible
let y = x  # create a view from a view
take y
# it is checked that 'y' does not outlive 'x' and
# that 'x' is not mutated as long as 'y' lives.

main(@[11, 22, 33]) A local variable of a view type can borrow from a location derived from a parameter, another local variable, a global const or let symbol or a thread- local var or let. Let p be the proc that is analyzed for the correctness of the borrow operation. Let source be one of: • A formal parameter of p. Note that this does not cover parameters of

 inner procs.

• The result symbol of p. • A local var or let or const of p. Note that this does not cover locals of

 inner procs.

• A thread-local var or let. • A global let or const. 220 • A constant array/seq/object/tuple constructor. 31.1. Path expressions A location derived from source is then defined as a path expression that has source as the owner. A path expression e is defined recursively: • source itself is a path expression. • Container access like e[i] is a path expression. • Tuple access e[0] is a path expression. • Object field access e.field is a path expression. • system.toOpenArray(e, ...) is a path expression. • Pointer dereference e[] is a path expression. • An address addr e, unsafeAddr e is a path expression. • A type conversion T(e) is a path expression. • A cast expression castT is a path expression. • f(e, ...) is a path expression if f's return type is a view type. Because the

 view can only have been borrowed from e, we then know that owner of
 f(e, ...) is e.

If a view type is used as a return type, the location must borrow from a location that is derived from the first parameter that is passed to the proc. See Section 21.10, “Var return type” for details about how this is done for var T. A mutable view can borrow from a mutable location, an immutable view can borrow from both a mutable or an immutable location. If a view borrows from a mutable location, the view can be used to update the location. Otherwise it cannot be used for mutations. The duration of a borrow is the span of commands beginning from the assignment to the view and ending with the last usage of the view. For the duration of the borrow operation, no mutations to the borrowed locations may be performed except via the view that borrowed from the location. The borrowed location is said to be sealed during the borrow.

                                                                        221

{.experimental: "views".} type

Obj = object
   field: string

proc dangerous(s: var seq[Obj]) =

let v: lent Obj = s[0] # seal 's'
s.setLen 0   # prevented at compile-time because 's' is sealed.
echo v.field

The scope of the view does not matter: {.experimental: "views".} type

Obj = object
   field: string

proc valid(s: var seq[Obj]) =

let v: lent Obj = s[0]   # begin of borrow
echo v.field             # end of borrow
s.setLen 0   # valid because 'v' isn't used afterward

The analysis requires as much precision about mutations as is reasonably obtainable, so it is more effective with the experimental strict funcs feature (see Chapter 30, Strict funcs). In other words --experimental:views:option: works better with --experimental:strictFuncs:option:. The analysis is currently control flow insensitive: proc invalid(s: var seq[Obj]) =

let v: lent Obj = s[0]
if false:
   s.setLen 0
echo v.field

In this example, the compiler assumes that s.setLen 0 invalidates the borrow operation of v even though a human being can easily see that it will never do that at runtime. 222 31.2. Start of a borrow A borrow starts with one of the following: • The assignment of a non-view-type to a view-type. • The assignment of a location that is derived from a local parameter to a

 view-type.

31.3. End of a borrow A borrow operation ends with the last usage of the view variable. 31.4. Reborrows A view v can borrow from multiple different locations. However, the borrow is always the full span of v's lifetime and every location that is borrowed from is sealed during v's lifetime.