Strings & Ownership
Strings are one of the first places where beginners ask:
“When do I need to
freethis?”
That is a good question.
The answer in cnegative is intentionally simple:
- string literals are just fixed program text
- some runtime operations create owned strings
- owned strings should be freed when you are done with them
Start with the easy case: string literals
let greeting:str = "hello";That literal is part of the program itself.
You can pass it around and compare it, but it is not the same thing as a heap-created runtime string.
There is now a second literal form for longer text:
let banner:str = `cnegative
rocks`;Use backticks when you want:
- multiline text
- text kept exactly as written
- no escape processing inside the literal
What is an owned string?
An owned string is a string value that the runtime created for you.
Because the runtime created it, you are responsible for freeing it later.
The good beginner rule is:
if the runtime had to make a new string for you, free it when you are done
The simplest owned-string example
fn:int main() {
let copied:str = str_copy("hello");
print(copied);
free copied;
return 0;
}What happened here:
"hello"is a literalstr_copy(...)creates a new owned stringfree copied;releases that owned string
Another common example: concatenation
fn:int main() {
let left:str = str_copy("hello");
let joined:str = str_concat(left, " world");
print(joined);
free left;
free joined;
return 0;
}Both left and joined are owned here, so both should be freed.
Input example
input() also creates a new runtime string:
fn:int main() {
let name:str = input();
print(name);
free name;
return 0;
}Current owned string producers
Right now, owned runtime strings come from:
input()str_copy(s)str_concat(a, b)std.io.read_line()std.strings.copy(s)std.strings.concat(a, b)std.strings.from_int(n)std.text.build(builder)on successstd.fs.read_text(path)on successstd.fs.cwd()on successstd.env.get(name)on successstd.path.join(...)std.path.file_name(...)std.path.stem(...)std.path.extension(...)std.path.parent(...)on successstd.net.join_host_port(...)std.net.recv(...)on success- the
hostanddatafields from successfulstd.net.udp_recv_from(...) std.ipc.stdout_read(...)on successstd.ipc.stdout_read_line(...)on successstd.ipc.request_line(...)on successstd.ipc.stderr_read(...)on successstd.ipc.stderr_read_line(...)on successstd.process.platform()std.process.arch()
std.lines.get(buffer, index) is different:
- it returns a borrowed line owned by the line buffer
- do not
freeit - it stops being safe to keep after
set,remove,clear, orrelease
slice T values are different too:
- a slice is only a view
- it does not own memory
free some_slice;is rejected withE3037
you do not need to memorize the full list
The practical beginner rule is enough: if the runtime created a new string for you, free it when you are done.
What if I free a literal?
In the current runtime, freeing a string literal is a safe no-op.
That does not mean you should rely on it as a style. It just means the runtime will not explode if you make that mistake.
Beginner summary
- literals like
"hello"are easy - copied/concatenated/read/input strings are usually owned
- owned strings should be freed
- this stays manual on purpose;
cnegativeis not trying to infer string ownership for you
current boundary
This is still a deliberately small ownership model, not a giant string runtime with many hidden rules.
Next step
If you want practical helper modules next, go to Standard Library Overview.