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
matchou 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.