Repository navigation
String Slice Lifetime
Home › All Categories › String Slices › String Slice Lifetime
A slice points into text it does not own. If that text is freed, or moved to a bigger block when its string grows, the slice reads whatever is in the old memory now. tiko rules this out in three layers:
- Where a slice may live. A slice is a non-escaping type: it can be a local, a parameter or a result, and nothing that could outlive the text it points into.
- Where it came from. Every slice has an origin — the string it borrows — and the compiler refuses a binding or a return that would outlast it.
-
What happens to the source meanwhile. Within a procedure, a string cannot be reassigned or grown while a slice of it is still to be read. Across procedures,
-exxchecks each read of a slice against its string.
No annotations are written for any of this. The rules are the same whether the program has one slice or a thousand, and a program with none pays nothing for them.
| Allowed | Refused |
|---|---|
| a local variable, in any block |
shared, static, common, extern (TKO1054) |
a module-level dim that is not shared (a local of the main program) |
an array element, static or dynamic (TKO1054) |
a parameter, byval or byref
|
a type argument: Array( of slice ) (TKO1054) |
| a function result (see Returning A Slice) | allocated with new (TKO1054) |
a field of a type declared as view
|
a field of an ordinary type (TKO1055) |
@v passed straight to a call |
@v stored anywhere (TKO1056) |
captured by a lambda or by spawn (TKO1060) |
dim shared as uslice g ' TKO1054: a uslice cannot be shared
dim as uslice arr( 3 ) ' TKO1054: a uslice cannot be an array elementA type may hold slices if it is declared a view:
type Lexer as view
src as const uslice
pos as integer
end typeas view ends the line; the body is an ordinary type body. A view follows every rule a slice does — where it may live, its origin, returns — so a Lexer cannot be shared, an array element or a field of an ordinary type. A type that holds a view is itself a view and must say so:
type Holder
lx as Lexer ' TKO1055: type 'holder' holds slice field 'lx' and must be declared 'as view'
end typeA view type declared in a module stays a view for every program that imports it. Generic view types are not supported.
std/strings' SplitSlices returns a view: the iterator holds the text and the separator, and hands out one part at a time, because an array of slices is not allowed.
The origin of a slice is the storage its text ultimately belongs to, found by following the expression back through fields, indexes, parentheses and further slicing:
| Origin | Lives | Examples |
|---|---|---|
| program | as long as the program | a literal, a named constant, a shared, static, extern or common string, anything reached through a pointer |
| caller | longer than this call | a byref string parameter, a slice or view parameter, a field reached through this
|
| local | the block it is declared in | a local string; a byval string parameter, which is the call's own copy |
| temporary | this statement | a function result, a concatenation, a string converted for a slice parameter |
A slice variable's origin is the shortest origin of everything bound into it.
A slice cannot be kept longer than its origin (TKO1057):
dim as uslice v
if ready then
dim as ustring inner = "abc"
v = inner[0 to 1] ' TKO1057: 'v' outlives 'inner', which this slice borrows from
end ifA slice of a temporary is used in its statement and nowhere else (TKO1058). Printing it, passing it to a call, or comparing it are all fine:
print (a & b)[0 to 3] ' fine
dim as const uslice v = (a & b)[0 to 3] ' TKO1058: this slice borrows from a temporary that ends with the statementiif over two slices takes the shorter-lived origin of the two arms.
A function may return a slice whose origin is caller or program. A slice of a local, or of a byval parameter, is TKO1059:
function FirstWord( byref s as const ustring ) as const uslice ' fine: the caller's text
return s[0 to 2]
end function
function Bad( byval s as ustring ) as const uslice
return s[0 to 2] ' TKO1059: cannot return a slice of local 's'
end functionAt the call site, the result is taken to borrow from every string passed byref, every slice or view argument, and this — whichever lives shortest. Nothing in the declaration says which argument the result really comes from, so the compiler assumes the worst. A call like FirstWord( a & b ) returns a slice of a temporary: it can be printed in the same statement, not kept.
A byref slice parameter, or a field of a byref view parameter or of this, may be given only a slice whose origin is caller or program (TKO1061). Otherwise a procedure could leave a slice of its own local in the caller's variable.
Between the line that makes a slice and the last line that reads it, the string it borrows from cannot be changed in any way that might move or shrink its text (TKO1062):
dim as ustring s = "hello"
dim as uslice v = s[0 to 2]
s &= " world" ' TKO1062: 's' cannot be changed while slice 'v' borrows from it (last used at line 4)
print vSwap the last two lines and the program compiles: v has been read for the last time before s grows. Pointing v at other text also ends the lock on s.
| Moves the text: refused while borrowed | Leaves it in place: allowed |
|---|---|
assignment, let, &=, += and the other compound forms |
s[i] = x |
swap |
the Mid statement, LSet, RSet
|
input, line input into it |
writes through a writable slice of it |
passing it byref to a parameter that is not const
|
reading it, passing it byval, byref as const, or as a slice |
Loops: a slice read in a loop body is read again by the next iteration, so a write later in the same body counts as before that read — TKO1062, unless the slice is pointed at new text first.
The lock is worked out within one procedure, in source order:
-
Branches are taken as if all of them run. A write in a
thenarm followed by a use in theelsearm is reported, though no run does both. -
gotoandgosubjumping backwards are not followed as loops are. -
Another name for the same string — a pointer, a second
byrefparameter that refers to it, asharedstring changed by a procedure this one calls — is not seen. That is what-exxis for.
Every slice of a string or ustring remembers which string it came from. Under -exx (or -earray), each read of such a slice — v[i], printing it, comparing it, converting it, slicing it again, the start of a for each over it — first checks that its text still lies inside that string's current text. If the string has moved or shrunk, the program stops with runtime error 6 at that line; an on error handler can catch it as any error 6. v[i] is also checked against the slice's own length.
#cmdline "-exx"
dim shared as ustring logText
sub Note( byref msg as const ustring )
logText &= msg & " -- long enough to move the text elsewhere"
end sub
sub Report( )
logText = "start"
dim as const uslice head = logText[0 to 4]
Note( "grown" ) ' changes logText; this procedure cannot see that
print head ' stops here: runtime error 6
end sub
Report( )Without -exx nothing is checked and nothing is slower: the word the check uses is filled in and never read. The same program then reads freed memory, and may well print start.
-
Pointer origins. A slice of a
zstring ptrorwstring ptris as valid as the pointer. It still cannot escape, but whether the memory is alive is the program's business. - Changes made in another procedure, without
-exx. -
The same block again. If a procedure reassigns the string and the allocator happens to hand back the same block at the same length, the
-exxcheck passes and the slice reads the new text. That is memory-safe, but it is not the old text. -
Fixed buffers rewritten in place. A
zstring * Ngiven a shorter value keeps its storage; a slice past the new NUL reads the old bytes, inside live memory. - Raw pointers to a slice's text, taken through a pointer cast, escape every rule, as raw pointers always do.
| Id | Message |
|---|---|
TKO1054 |
a ‹type› cannot be shared / static / common / extern / an array element / a generic type argument / allocated with new |
TKO1055 |
type '‹name›' holds slice field '‹field›' and must be declared 'as view' |
TKO1056 |
cannot take the address of slice '‹name›' except to pass it |
TKO1057 |
'‹slice›' outlives '‹string›', which this slice borrows from |
TKO1058 |
this slice borrows from a temporary that ends with the statement |
TKO1059 |
cannot return a slice of local '‹name›' / of a temporary |
TKO1060 |
a lambda / 'spawn' cannot capture slice '‹name›' |
TKO1061 |
a slice stored through byref parameter '‹name›' must outlive the call |
TKO1062 |
'‹string›' cannot be changed while slice '‹slice›' borrows from it (last used at line ‹n›) |
See also: String Slices, Compiler Error Messages.
tiko-lang · Home · Feature Index · Keyword Index · License
FreeBASIC manual pages reproduced under the GNU FDL 1.3.
- Generics
- Interfaces
- For Each
- Iterators
- Tuples
- Array Results
- Defer
- Lambdas
- Block Strings
- Partial Types
- String Slices
- Embed
- Typed Variadics
- C Header Import
- Foreign Library Linking
- UString
- Fixed-Width Types
- Nullptr
- String Library
- Format
- Array
- Map
- Set
- LinkedList
- OrderedMap
- OrderedSet
- Sorting
- Optional
- Result
- Smart Pointers
- Date and Time (Chrono)
- Filesystem
- File Streams
- Config (INI and TOML)
- Codec
- Hash
- UUID
- OS
- Process
- Net
- HTTP and TLS
- Math Library
- Finance
- Logging
- Terminal TUI Library
- PsControls
- tkControls
- sys/win
- Goroutines and Channels
- LLVM Backend
- tiko build
- TIKO_COMPILER
- Variable Declarations
- Standard Data Types
- User Defined Types
- Procedures
- Operators
- Control Flow Statements
- Converting Data Types
- Array Functions
- String Functions
- Mathematical Functions
- Bit Manipulation
- Date and Time Functions
- File I/O Functions
- Console Functions
- User Input (Runtime Library)
- User Input Functions (Graphics Library)
- Screen Functions
- 2D Drawing Functions
- Graphics Library Reference
- Graphics Keyword List
- Memory Functions
- Threading Support Functions
- Error Handling Functions
- Operating System Functions
- Preprocessor
- Intrinsic Defines
- Miscellaneous Keywords
- Modularizing