A cobertura de tradução é parcial. Consulte a fonte em inglês para a referência técnica completa. Ver versão em inglês →

Tipos Lineares e Posse (Ownership)

Gestão segura de recursos através de tipos lineares, referências exclusivas e semântica de transferência (move).

Tipos Lineares e Ownership

O Sounio garante a segurança de recursos por meio de tipos lineares. Um valor linear deve ser usado exatamente uma vez: não é possível descartar silenciosamente um file handle, uma conexão de banco de dados ou um buffer de GPU. O compilador rejeita programas que esquecem de consumir recursos lineares.

Structs lineares

Marque uma struct como linear para exigir consumo exatamente uma vez:

linear struct FileHandle {
    fd: i32
}

fn open_file(path: &str) -> FileHandle with IO {
    // Abertura em nível de SO; retorna um handle linear
    FileHandle { fd: sys_open(path) }
}

fn close_file(handle: FileHandle) with IO {
    sys_close(handle.fd)
    // handle é consumido aqui — o compilador fica satisfeito
}

fn main() -> i32 with IO {
    let h = open_file("/tmp/data.txt")
    // Se esquecermos close_file(h), o compilador rejeita este programa.
    close_file(h)
    return 0
}

Se você remover a chamada close_file(h), o compilador emite um erro: o valor linear h não foi consumido.

Referências: compartilhadas vs. exclusivas

O Sounio usa &T para referências compartilhadas (somente leitura) e &!T para referências exclusivas (mutáveis). Isso não é o &mut do Rust — o sigilo &! sinaliza acesso exclusivo.

fn read_value(r: &i64) -> i64 {
    *r
}

fn increment(r: &!i64) with Mut {
    *r = *r + 1
}

fn main() -> i32 with IO, Mut {
    var x = 10
    println(read_value(&x))   // empréstimo compartilhado
    increment(&!x)            // empréstimo exclusivo
    println(x)                // imprime 11
}

O compilador impede que uma referência exclusiva coexista com qualquer outra referência ao mesmo valor.

Semântica de move

Valores que não são Copy são movidos na atribuição. Depois de um move, o binding original deixa de ser válido:

fn consume(h: FileHandle) with IO {
    close_file(h)
    return 0
}

fn main() -> i32 with IO {
    let h = open_file("/tmp/data.txt")
    consume(h)
    // h não é mais válido aqui — usá-lo é um erro de compilação
}

Relaxamento afim

Alguns tipos são afins em vez de estritamente lineares: devem ser usados no máximo uma vez, mas podem ser descartados silenciosamente. Tipos escalares padrão (i32, i64, f64, bool) são Copy e podem ser usados livremente. Structs personalizadas sem a palavra-chave linear seguem semântica de move, mas não são obrigadas a serem consumidas.

Onde a verificação de ownership vive

O verificador de ownership é implementado em self-hosted/check/ownership.sio e roda depois da verificação de tipos. Ele rastreia quais bindings foram movidos, quais estão emprestados, e aplica o contrato de linearidade para tipos linear struct.

Ressalvas

  • Campos lineares aninhados dentro de structs não lineares são rastreados de forma recursiva.
  • O checker atual ainda não trata todos os padrões envolvendo valores lineares dentro de braços de match ou closures — consulte a página de status de recursos para a cobertura atual.
  • A mutação via &! de um array simples (&![T; N]) tem uma limitação conhecida (CLAUDE.md §13): prefira um struct envoltório, (*arr)[i] ou padrões de retorno por valor para mutações que precisam ser visíveis ao chamador.