Ownership

August 17, 2026 · View on GitHub

V has an optional ownership system inspired by Rust that tracks owned values and prevents use-after-move bugs at compile time. It is currently focused on strings and is enabled with the -ownership flag.

Quick start

fn main() {
	s1 := 'hello'.to_owned()
	s2 := s1 // s1 is moved to s2
	println(s1) // error: use of moved value: `s1`
}

Compile with ownership checking:

v -ownership -o out main.v

Ownership mode defines the target-visible custom option ownership. Code can use $if ownership ? {} to select ownership-specific branches, and files named *_d_ownership.v are included in ownership builds.

Creating owned values

Call .to_owned() on a string to create an owned copy. Only strings created with .to_owned() participate in ownership tracking — regular string literals and primitive types (int, f64, bool, ...) are unaffected.

s := 'hello'.to_owned() // s is owned
t := 'world' // t is a normal string, no ownership tracking

Move semantics

Assigning an owned value to another variable moves it. The original variable becomes unusable:

s1 := 'hello'.to_owned()
s2 := s1 // move
println(s1) // error: use of moved value: `s1`

Passing an owned value to a function also moves it:

fn takes_ownership(s string) {
	println(s)
}

fn main() {
	s := 'hello'.to_owned()
	takes_ownership(s)
	println(s) // error: use of moved value: `s`
}

Preventing moves with .clone()

Use .clone() to make an independent copy instead of moving:

s1 := 'hello'.to_owned()
s2 := s1.clone() // s1 is NOT moved
println(s1) // ok
println(s2) // ok
takes_ownership(s.clone()) // s is NOT moved
println(s) // ok

Borrowing

Pass &variable to borrow without moving. The original stays usable:

fn calculate_length(s &string) int {
	return s.len
}

fn main() {
	s := 'hello'.to_owned()
	len := calculate_length(&s)
	println(s) // ok — s was borrowed, not moved
}

Explicit lifetimes

Ownership mode also supports explicit named lifetimes with ^name.

Use &^a T for a borrowed reference with an explicit lifetime and [^a] in generic parameter and argument lists:

struct Ignore {}

struct IgnoreMatch[^a] {}

fn matched_dir_entry[^a](self &^a Ignore) IgnoreMatch[^a] {
	return IgnoreMatch[^a]{}
}

^ is used instead of Rust's ' because ' is already used for string and character literals in V.

Multiple immutable borrows are allowed:

s := 'hello'.to_owned()
r1 := &s
r2 := &s // ok

Mutable borrows via mut parameters work too — the variable is usable after the call returns:

fn append_world(mut s string) {
	s = s + ' world'
}

fn main() {
	mut s := 'hello'.to_owned()
	append_world(mut s)
	println(s) // ok — prints "hello world"
}

Borrow restrictions

A borrowed variable cannot be moved or reassigned while the borrow is active:

s := 'hello'.to_owned()
r := &s
s2 := s // error: cannot move `s` because it is borrowed
mut s := 'hello'.to_owned()
r := &s
s = 'world'.to_owned() // error: cannot assign to `s` because it is borrowed

Return value ownership

Functions that create and return owned values transfer ownership to the caller:

fn gives_ownership() string {
	return 'hello'.to_owned()
}

fn main() {
	s1 := gives_ownership() // s1 is owned
	s2 := s1 // move
	println(s1) // error: use of moved value
}

Functions that return a parameter pass ownership through:

fn takes_and_gives_back(s string) string {
	return s
}

fn main() {
	s1 := 'hello'.to_owned()
	s2 := takes_and_gives_back(s1) // s1 moved in, ownership comes back as s2
	println(s1) // error: s1 was moved
	println(s2) // ok
}

Full example

From the Rust book, translated to V:

fn gives_ownership() string {
	s := 'hello'.to_owned()
	return s
}

fn takes_and_gives_back(a_string string) string {
	return a_string
}

fn main() {
	s1 := gives_ownership()
	s2 := 'hello'.to_owned()
	s3 := takes_and_gives_back(s2)
	println(s1) // ok
	println(s3) // ok
}

Enabling ownership checking

Ownership checking is compiled into a separate v3_ownership binary using V's compile-time defines so there is no ownership-checking overhead in the normal compiler.

v -ownership file.v        # check and compile

The main V driver forwards -d ownership to the ownership-enabled compiler. This both enables the target compile-time checks described above and selects ownership-specific files.

To build the ownership-enabled compiler manually:

v -d ownership -o v3_ownership vlib/v3/v3.v