mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-09 06:23:25 +00:00
move "Strict definitions and out parameters" to the manual (#24474)
This commit is contained in:
116
doc/manual.md
116
doc/manual.md
@@ -9043,3 +9043,119 @@ This means the following compiles (for now) even though it really should not:
|
||||
inc i
|
||||
access a[i].v
|
||||
```
|
||||
|
||||
Strict definitions and `out` parameters
|
||||
=======================================
|
||||
|
||||
*every* local variable must be initialized explicitly before it can be used:
|
||||
|
||||
```nim
|
||||
proc test =
|
||||
var s: seq[string]
|
||||
s.add "abc" # invalid!
|
||||
```
|
||||
|
||||
Needs to be written as:
|
||||
|
||||
```nim
|
||||
proc test =
|
||||
var s: seq[string] = @[]
|
||||
s.add "abc" # valid!
|
||||
```
|
||||
|
||||
A control flow analysis is performed in order to prove that a variable has been written to
|
||||
before it is used. Thus the following is valid:
|
||||
|
||||
```nim
|
||||
proc test(cond: bool) =
|
||||
var s: seq[string]
|
||||
if cond:
|
||||
s = @["y"]
|
||||
else:
|
||||
s = @[]
|
||||
s.add "abc" # valid!
|
||||
```
|
||||
|
||||
In this example every path does set `s` to a value before it is used.
|
||||
|
||||
```nim
|
||||
proc test(cond: bool) =
|
||||
let s: seq[string]
|
||||
if cond:
|
||||
s = @["y"]
|
||||
else:
|
||||
s = @[]
|
||||
```
|
||||
|
||||
`let` statements are allowed to not have an initial value, but every path should set `s` to a value before it is used.
|
||||
|
||||
|
||||
`out` parameters
|
||||
----------------
|
||||
|
||||
An `out` parameter is like a `var` parameter but it must be written to before it can be used:
|
||||
|
||||
```nim
|
||||
proc myopen(f: out File; name: string): bool =
|
||||
f = default(File)
|
||||
result = open(f, name)
|
||||
```
|
||||
|
||||
While it is usually the better style to use the return type in order to return results API and ABI
|
||||
considerations might make this infeasible. Like for `var T` Nim maps `out T` to a hidden pointer.
|
||||
For example POSIX's `stat` routine can be wrapped as:
|
||||
|
||||
```nim
|
||||
proc stat*(a1: cstring, a2: out Stat): cint {.importc, header: "<sys/stat.h>".}
|
||||
```
|
||||
|
||||
When the implementation of a routine with output parameters is analysed, the compiler
|
||||
checks that every path before the (implicit or explicit) return does set every output
|
||||
parameter:
|
||||
|
||||
```nim
|
||||
proc p(x: out int; y: out string; cond: bool) =
|
||||
x = 4
|
||||
if cond:
|
||||
y = "abc"
|
||||
# error: not every path initializes 'y'
|
||||
```
|
||||
|
||||
|
||||
Out parameters and exception handling
|
||||
-------------------------------------
|
||||
|
||||
The analysis should take exceptions into account (but currently does not):
|
||||
|
||||
```nim
|
||||
proc p(x: out int; y: out string; cond: bool) =
|
||||
x = canRaise(45)
|
||||
y = "abc" # <-- error: not every path initializes 'y'
|
||||
```
|
||||
|
||||
Once the implementation takes exceptions into account it is easy enough to
|
||||
use `outParam = default(typeof(outParam))` in the beginning of the proc body.
|
||||
|
||||
Out parameters and inheritance
|
||||
------------------------------
|
||||
|
||||
It is not valid to pass an lvalue of a supertype to an `out T` parameter:
|
||||
|
||||
```nim
|
||||
type
|
||||
Superclass = object of RootObj
|
||||
a: int
|
||||
Subclass = object of Superclass
|
||||
s: string
|
||||
|
||||
proc init(x: out Superclass) =
|
||||
x = Superclass(a: 8)
|
||||
|
||||
var v: Subclass
|
||||
init v
|
||||
use v.s # the 's' field was never initialized!
|
||||
```
|
||||
|
||||
However, in the future this could be allowed and provide a better way to write object
|
||||
constructors that take inheritance into account.
|
||||
|
||||
|
||||
@@ -1919,136 +1919,6 @@ restrictions / changes:
|
||||
yet performed for ordinary slices outside of a `parallel` section.
|
||||
|
||||
|
||||
Strict definitions and `out` parameters
|
||||
=======================================
|
||||
|
||||
With `experimental: "strictDefs"` *every* local variable must be initialized explicitly before it can be used:
|
||||
|
||||
```nim
|
||||
{.experimental: "strictDefs".}
|
||||
|
||||
proc test =
|
||||
var s: seq[string]
|
||||
s.add "abc" # invalid!
|
||||
|
||||
```
|
||||
|
||||
Needs to be written as:
|
||||
|
||||
```nim
|
||||
{.experimental: "strictDefs".}
|
||||
|
||||
proc test =
|
||||
var s: seq[string] = @[]
|
||||
s.add "abc" # valid!
|
||||
|
||||
```
|
||||
|
||||
A control flow analysis is performed in order to prove that a variable has been written to
|
||||
before it is used. Thus the following is valid:
|
||||
|
||||
```nim
|
||||
{.experimental: "strictDefs".}
|
||||
|
||||
proc test(cond: bool) =
|
||||
var s: seq[string]
|
||||
if cond:
|
||||
s = @["y"]
|
||||
else:
|
||||
s = @[]
|
||||
s.add "abc" # valid!
|
||||
```
|
||||
|
||||
In this example every path does set `s` to a value before it is used.
|
||||
|
||||
```nim
|
||||
{.experimental: "strictDefs".}
|
||||
|
||||
proc test(cond: bool) =
|
||||
let s: seq[string]
|
||||
if cond:
|
||||
s = @["y"]
|
||||
else:
|
||||
s = @[]
|
||||
```
|
||||
|
||||
With `experimental: "strictDefs"`, `let` statements are allowed to not have an initial value, but every path should set `s` to a value before it is used.
|
||||
|
||||
|
||||
`out` parameters
|
||||
----------------
|
||||
|
||||
An `out` parameter is like a `var` parameter but it must be written to before it can be used:
|
||||
|
||||
```nim
|
||||
proc myopen(f: out File; name: string): bool =
|
||||
f = default(File)
|
||||
result = open(f, name)
|
||||
```
|
||||
|
||||
While it is usually the better style to use the return type in order to return results API and ABI
|
||||
considerations might make this infeasible. Like for `var T` Nim maps `out T` to a hidden pointer.
|
||||
For example POSIX's `stat` routine can be wrapped as:
|
||||
|
||||
```nim
|
||||
proc stat*(a1: cstring, a2: out Stat): cint {.importc, header: "<sys/stat.h>".}
|
||||
```
|
||||
|
||||
When the implementation of a routine with output parameters is analysed, the compiler
|
||||
checks that every path before the (implicit or explicit) return does set every output
|
||||
parameter:
|
||||
|
||||
```nim
|
||||
proc p(x: out int; y: out string; cond: bool) =
|
||||
x = 4
|
||||
if cond:
|
||||
y = "abc"
|
||||
# error: not every path initializes 'y'
|
||||
```
|
||||
|
||||
|
||||
Out parameters and exception handling
|
||||
-------------------------------------
|
||||
|
||||
The analysis should take exceptions into account (but currently does not):
|
||||
|
||||
```nim
|
||||
proc p(x: out int; y: out string; cond: bool) =
|
||||
x = canRaise(45)
|
||||
y = "abc" # <-- error: not every path initializes 'y'
|
||||
```
|
||||
|
||||
Once the implementation takes exceptions into account it is easy enough to
|
||||
use `outParam = default(typeof(outParam))` in the beginning of the proc body.
|
||||
|
||||
Out parameters and inheritance
|
||||
------------------------------
|
||||
|
||||
It is not valid to pass an lvalue of a supertype to an `out T` parameter:
|
||||
|
||||
```nim
|
||||
type
|
||||
Superclass = object of RootObj
|
||||
a: int
|
||||
Subclass = object of Superclass
|
||||
s: string
|
||||
|
||||
proc init(x: out Superclass) =
|
||||
x = Superclass(a: 8)
|
||||
|
||||
var v: Subclass
|
||||
init v
|
||||
use v.s # the 's' field was never initialized!
|
||||
```
|
||||
|
||||
However, in the future this could be allowed and provide a better way to write object
|
||||
constructors that take inheritance into account.
|
||||
|
||||
|
||||
**Note**: The implementation of "strict definitions" and "out parameters" is experimental but the concept
|
||||
is solid and it is expected that eventually this mode becomes the default in later versions.
|
||||
|
||||
|
||||
Strict case objects
|
||||
===================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user