Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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()

Pitfall

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

  1. Change Before to take byval s as ustring and read the error.
  2. Move print initial above the &= in slicelock.tiko and run it.