OxL Справочник OxL
РусскийEnglish

Шаг 7. Владение и заимствование

Зачем#

В языках без сборщика мусора программист отвечает за освобождение памяти; в языках со сборщиком платят паузами и непредсказуемостью. OxL выбирает третий путь: у каждого значения ровно один владелец; когда владелец выходит из области видимости, значение уничтожается. Из этого одного правила следует отсутствие утечек, двойного освобождения и гонок данных между задачами — и всё это проверяется во время компиляции. Явных «времён жизни» (как в Rust) нет: проверка устроена проще (§7.2).

Три правила:

  1. Присваивание, передача в функцию и возврат перемещают значение (типы Copy — числа, str, bool — просто копируются).
  2. &x — разделяемое заимствование (только чтение), &var x — исключительное (чтение и запись). Пока есть &var, других ссылок нет; пока есть &, менять нельзя.
  3. x.clone() — явная независимая копия.

Пример#

oxl✓ проверяется в CI
fn total(xs: List<i32>) -> i32  pure {  // параметр по значению: функция становится владельцем
  var s = 0
  for x in xs { s += x }
  s
}

fn count(xs: &List<i32>) -> usize  pure => xs.len()  // &T — чтение без передачи владения

fn addOne(xs: &var List<i32>) {  // &var T — исключительное изменяемое заимствование
  xs.push(1)
}

fn main() io {
  var xs = [10, 20, 30]
  print("{count(&xs)}")  // &xs — одолжить; xs остаётся нашим
  addOne(&var xs)  // &var нужно писать явно
  let copy = xs.clone()  // clone — независимая копия
  print("{total(xs)}")  // xs перемещён в total; дальше его использовать нельзя
  print("{copy.len()} {copy}")
}
Вывод
3
61
4 [10, 20, 30, 1]

Разбор#

  • total(xs: List<i32>) — параметр по значению: вызвав total(xs), вы отдали список. После этого xs использовать нельзя.
  • count(xs: &List<i32>) и вызов count(&xs) — функция только читает и ничего не забирает.
  • addOne(&var xs) — &var пишется явно на месте вызова: читатель сразу видит, что функция может изменить аргумент.
  • clone() дорогое (копирует данные), поэтому оно никогда не происходит неявно.

Типичные ошибки#

Использование после перемещения:

oxl✓ проверяется в CI
fn main() {
  let xs = [1, 2, 3]
  let ys = xs  // значение переехало в ys
  print(xs.len())  // ошибка: xs больше не владеет списком
}
error
use-after-move

Диагностика use-after-move подсвечивает и место перемещения. Исправления: передать &xs, сделать xs.clone() или вернуть переданное значение из функции.

Изменение во время чтения:

oxl✓ проверяется в CI
fn main() {
  var xs = [1, 2, 3]
  let first = &xs
  xs.push(4)  // изменять нельзя, пока жив &xs
  print(first.len())
}
error
borrow-conflict

borrow-conflict: пока first держит заимствование списка, push запрещён — иначе указатель мог бы повиснуть после перераспределения памяти. Заимствование живёт до последнего использования, поэтому достаточно переставить строки. Ссылки нельзя хранить в полях data и передавать в другие задачи (borrow-escape): для общих данных используется freeze(x) — неизменяемое значение, которое можно разделять между задачами (§7.3).

Что запомнить#

  • Один владелец; перемещение по умолчанию; & читать, &var менять, clone копировать.
  • Типы Copy (числа, bool, str, bytes) перемещения не боятся.
  • Ошибки владения — ошибки компиляции, а не падения в рантайме.

Дальше: эффекты.

См. также

OxL 1.0.0-beta · Версия документации 1.0.0-beta© OxwaldLink · сгенерировано из Стандарта, исходников std и руководства