Você sabia que existe um modo inseguro na linguagem rust?
Resumo
Hoje eu quero falar sobre o unsafe rust, uma parte da linguagem rust que nos permite escrever códigos que podem comprometer a segurança de memória e concorrência que o Rust nos garante. Mas se ele compromete a segurança, por que precisamos dele?
Rust , segurança e borrow check
Antes de tudo, precisamos entender que o Rust é uma linguagem muito segura e performática, essa segurança é garantida pela forma como ela gerencia a memória, conhecida como ownership, uma abordagem diferente de outras linguagens que usam o garbage collector ou alocação explicita.
Além dessa abordagem, o rust dispõe de outras garantias de segurança, como empréstimo, lifetimes e traits, que são verificadas em tempo de compilação por meio de um sistema chamado borrow check. Esse sistema verifica se o código respeita as regras de propriedade e empréstimo de valores, evitando erros de acesso inválidos a ponteiros, uso após liberação, corrupção de dados e etc.
As regras do Ownership:
1. Cada valor tem uma variável que é sua dona
2. Só pode haver uma dona por vez
3. Quando a dona sai do escopo, o valor é apagado
Quando usar o Rust Unsafe
No entanto, as vezes precisamos escrever códigos que o compilador não consegue entender ou verificar, ou seja, a garantia de segurança fica nas mãos do desenvolvedor. Para que fique ainda mais claro, ativar o modo unsafe é como se o rust dissesse assim: “Ok, a segurança desse negócio está em suas mãos”. Mas em quais momentos isso é necessário?
- Criar uma variável global, ou seja, que pode ser modificada em qualquer parte do código (e não apenas dentro do seu escopo);
- Interagir com código externo e usar instruções de baixo nível;
- Desreferenciar um ponteiro bruto – obter o valor que ele aponta;
- Implementar uma trait unsafe
- Acessar campos de uma união – um tipo de dado que pode guardar diferentes tipos de valores no mesmo endereço de memória, diferente de uma struct.
Obs. O rust unsafe não desativa as verificações de segurança do rust, ela apenas nos dá acesso aos recursos que não são verificados pelo compilador.
Exemplos de uso
Veja o exemplo dessa aplicação que simula um caixa eletrônico, no primeiro exemplo nós temos uma função main recebendo uma variável total_coins, seu valor é repassado no parâmetro das funções. No segundo exemplo nós temos uma variável global TOTAL_COINS, onde seu valor pode ser usado em qualquer lugar do código
Sem o rust unsafe
//sem o modo unsafe,
fn main() {
//a variável está dentro da função
let total_coins: f64 = 0.0;
println!("Bem vindo ao seu internet banking");
println!("escolha a sua opção: ");
println!("1 - Depositar");
println!("2 - Sacar");
println!("Insira a sua opção aqui: ");
let mut option = String::new();
io::stdin().read_line(&mut option).expect("Erro de entrada");
if option.contains("1") {
//seu valor é passado como argumento
insert_money(deposit: total_coins)
} else if option.contains("2") {
//seu valor é passado como argumento
get_money(discredit: total_coins)
} else {
println!("Opção inválida")
}
}
Com o rust Unsafe
//criamos uma variável global no escopo global
static mut TOTAL_COINS: f64 = 0.0;
//a função não tem parâmetro,
fn insert_money() {
unsafe { //chamamos o modo unsafe
let coins = [2.00, 5.00, 10.00, 20.00, 50.00, 100.00];
loop {
println!("Insira quanto você deseja investir: ");
let mut credit = String::new();
io::stdin()
.read_line(&mut credit)
.expect("Falha ao ler entrada");
let credit = credit.trim().parse().expect("valor inválidos");
if coins.contains(&credit) {
//usamos a varivel TOTAL_COINS
TOTAL_COINS = credit + TOTAL_COINS;
println!("Você inseriu {} e seu saldo é {}", credit, TOTAL_COINS)
} else if credit == 0.0 {
main()
} else {
println!("Ocorreu um erro")
}
}
}
}