Slices and lifetime
A slice points into memory it does not own. If the string it came from goes away, or moves its text somewhere else, the slice would read whatever is there now. The compiler keeps track of where each slice came from and refuses the programs where that could happen.
'' A function may return a slice of text its caller owns: here s is
'' byref, so the text outlives the call.
function Before( byref s as const ustring, byval sep as int32 ) _
as const uslice
for i as int32 = 0 to len( s ) - 1
if s[i] = sep then return s[0 to i - 1]
next
return s[0 to len( s ) - 1]
end function
dim as ustring path = "docs/guide/intro.md"
dim as const uslice top = Before( path, asc( "/" ) )
print "top folder: "; top
'' A type that holds a slice is declared "as view". It follows the
'' same rules as a slice: no shared, no arrays, no new.
type Cursor as view
text as const uslice
pos as int32
end type
function Take( byref c as Cursor, byval n as int32 ) as const uslice
dim as int32 start = c.pos
c.pos += n
if c.pos > len( c.text ) then c.pos = len( c.text )
return c.text[start to c.pos - 1]
end function
dim as Cursor c
c.text = path[0 to len( path ) - 1]
print Take( c, 4 ); " | "; Take( c, 6 ); " | "; Take( c, 99 )
Output
top folder: docs
docs | /guide | /intro.md
A function may return a slice of its caller's text. Before takes s
byref, so the text belongs to the caller and is still there after the call.
With byval s, s is the call's own copy, and returning a slice of it is
error TKO1059.
A slice lives in a local, a parameter or a result, nowhere else. It
cannot be shared or static, an array element, a container element or
allocated with new (TKO1054), and it cannot be kept past the end of the
block, or the statement, its text belongs to (TKO1057, TKO1058).
A type that holds a slice is declared as view, and follows the same
rules: Cursor can be a local, a parameter or a result, and an ordinary type
cannot hold one (TKO1055).
While a slice is in use, the string it came from cannot change size:
dim as ustring name_ = "Ada"
dim as const uslice initial = name_[0 to 0]
'' Appending may move the text to a bigger block of memory, which
'' would leave initial pointing at the old one.
name_ &= " Lovelace"
print initial
slicelock.tiko(6,1): error TKO1062: 'name_' cannot be changed while slice 'initial' borrows from it (last used at line 7)
|
6 | name_ &= " Lovelace"
| ^
Assigning, appending, swap, input and passing the string byref to
something that may change it all count, up to the last line that reads the
slice. Move the print above the &= and it compiles. Writing one unit,
name_[0] = x, or the mid statement never moves the text, and is allowed.
The compiler sees one procedure at a time. When another procedure changes
the string, -exx catches it when the slice is next read:
#cmdline "-exx"
dim shared as ustring buffer
sub AddText( byref more as const ustring )
buffer &= more & more & more & more
end sub
sub Show( )
buffer = "abc"
dim as const uslice view_ = buffer[0 to 2]
'' The compiler cannot see that AddText changes buffer.
AddText( "a line long enough to need a new block of memory" )
'' -exx checks view_ is still inside buffer: it is not.
print view_
end sub
Show( )
Aborting due to runtime error 6 (out of bounds array access) at line 15 of slicecheck.tiko::SHOW()
Without -exx the same program reads freed memory and may well print abc,
because nothing has reused it yet. Build with -exx while you develop. A
slice of a zstring ptr has nothing to check against: the pointer is
trusted, as it is everywhere else.
Activities
- Change
Beforeto takebyval s as ustringand read the error. - Move
print initialabove the&=inslicelock.tikoand run it.