Rust

Guia Completo de Unsafe Rust: Quando e Como Usar com Responsabilidade

7 min de leitura

Guia Completo de Unsafe Rust: Quando e Como Usar com Responsabilidade

Entendendo Unsafe Rust Unsafe Rust é um subconjunto da linguagem que permite operações que o compilador não consegue verificar automaticamente. Contrariamente ao que muitos acreditam, "unsafe" não significa "sem segurança" — significa que você assume a responsabilidade de garantir a segurança. O compilador confia em você para não introduzir comportamentos indefinidos (undefined behavior). A razão de existir é prática: algumas operações são necessárias em sistemas de baixo nível, chamadas de funções C, manipulação de ponteiros e otimizações críticas. Rust oferece as ferramentas, mas exige que você entenda as consequências. Isso é um contrato entre você e a linguagem. Operações Unsafe Principais Desreferenciar Ponteiros Brutos Ponteiros brutos ( e ) ignoram as regras de borrowing do Rust. Você pode ter múltiplos ponteiros mutáveis apontando para o mesmo local, causando data races se não tiver cuidado. Desreferenciar um ponteiro inválido é undefined behavior. Este exemplo é seguro porque sabemos que aponta para durante toda sua vida útil. Porém, nunca dereference um ponteiro

<h2>Entendendo Unsafe Rust</h2>

<p>Unsafe Rust é um subconjunto da linguagem que permite operações que o compilador não consegue verificar automaticamente. Contrariamente ao que muitos acreditam, &quot;unsafe&quot; não significa &quot;sem segurança&quot; — significa que <strong>você assume a responsabilidade</strong> de garantir a segurança. O compilador confia em você para não introduzir comportamentos indefinidos (undefined behavior).</p>

<p>A razão de existir é prática: algumas operações são necessárias em sistemas de baixo nível, chamadas de funções C, manipulação de ponteiros e otimizações críticas. Rust oferece as ferramentas, mas exige que você entenda as consequências. Isso é um contrato entre você e a linguagem.</p>

<h2>Operações Unsafe Principais</h2>

<h3>Desreferenciar Ponteiros Brutos</h3>

<p>Ponteiros brutos (<code><em>const T</code> e <code></em>mut T</code>) ignoram as regras de borrowing do Rust. Você pode ter múltiplos ponteiros mutáveis apontando para o mesmo local, causando data races se não tiver cuidado. Desreferenciar um ponteiro inválido é undefined behavior.</p>

<pre><code class="language-rust">fn main() {

let x = 5;

let raw_ptr = &amp;x as *const i32;

unsafe {

println!(&quot;Valor: {}&quot;, *raw_ptr); // Válido, aponta para x

}

}</code></pre>

<p>Este exemplo é seguro porque sabemos que <code>raw_ptr</code> aponta para <code>x</code> durante toda sua vida útil. Porém, nunca dereference um ponteiro nulo ou inválido.</p>

<h3>Chamar Funções Unsafe</h3>

<p>Funções externas (FFI) e algumas funções Rust marcadas como <code>unsafe</code> precisam estar dentro de blocos unsafe. A função sinaliza: &quot;eu tenho precondições que você deve garantir&quot;.</p>

<pre><code class="language-rust">extern &quot;C&quot; {

fn strlen(s: *const u8) -&gt; usize;

}

fn main() {

let c_string = b&quot;hello\0&quot;;

unsafe {

let len = strlen(c_string.as_ptr());

println!(&quot;Comprimento: {}&quot;, len);

}

}</code></pre>

<p>Aqui, <code>strlen</code> requer que o ponteiro seja válido e termine com <code>\0</code>. É sua responsabilidade garantir isso. Se passar um ponteiro inválido, comportamento indefinido ocorrerá.</p>

<h3>Mutabilidade Estática e Raw Pointers</h3>

<p>Variáveis estáticas mutáveis são inerentemente thread-unsafe. Acessá-las exige <code>unsafe</code> porque múltiplas threads podem modificá-las simultaneamente.</p>

<pre><code class="language-rust">static mut COUNTER: i32 = 0;

fn increment() {

unsafe {

COUNTER += 1;

}

}

fn main() {

increment();

unsafe {

println!(&quot;Contador: {}&quot;, COUNTER);

}

}</code></pre>

<p>Se você precisar de um contador thread-safe, use <code>std::sync::atomic::AtomicI32</code> em vez disso — é seguro e não requer <code>unsafe</code>.</p>

<h2>Quando Usar (e Não Usar) Unsafe</h2>

<h3>Casos Legítimos</h3>

<p>Use unsafe apenas quando for <strong>absolutamente necessário</strong>: integração com C, implementar estruturas de dados de baixo nível (alocadores customizados, concorrência primitiva), ou quando a performance crítica é bloqueadora. Mesmo assim, minimize o escopo do bloco <code>unsafe</code>.</p>

<pre><code class="language-rust">pub struct RawBuffer {

ptr: *mut u8,

capacity: usize,

}

impl RawBuffer {

pub fn new(capacity: usize) -&gt; Self {

let ptr = unsafe {

std::alloc::alloc(std::alloc::Layout::from_size_align_unchecked(

capacity, 1

)) as *mut u8

};

RawBuffer { ptr, capacity }

}

pub fn get(&amp;self, index: usize) -&gt; Option&lt;u8&gt; {

if index &lt; self.capacity {

unsafe { Some(*self.ptr.add(index)) }

} else {

None

}

}

}

impl Drop for RawBuffer {

fn drop(&amp;mut self) {

unsafe {

std::alloc::dealloc(

self.ptr,

std::alloc::Layout::from_size_align_unchecked(

self.capacity, 1

)

);

}

}

}</code></pre>

<p>Este exemplo encapsula a complexidade: o API público é totalmente seguro, e <code>unsafe</code> fica contido internamente com verificações adequadas.</p>

<h3>Antipadrões Comuns</h3>

<p>Não use <code>unsafe</code> para:</p>

<ul>

<li><strong>Ignorar o type system</strong>: Se o compilador reclama, há uma razão.</li>

<li><strong>Ganho de performance especulativo</strong>: Meça primeiro. <code>unsafe</code> muitas vezes não oferece ganhos reais.</li>

<li><strong>Contornar regras de borrowing</strong> porque &quot;você sabe melhor&quot;: Você provavelmente não sabe. As regras existem por razão.</li>

</ul>

<h2>Boas Práticas e Responsabilidade</h2>

<p>Quando você escreve <code>unsafe</code>, coloque um comentário detalhado explicando <strong>por que</strong> é seguro. Isso ajuda revisores de código e você mesmo no futuro.</p>

<pre><code class="language-rust">pub fn safe_slice_from_raw(ptr: *const u8, len: usize) -&gt; Option&lt;&amp;&#039;static [u8]&gt; {

unsafe {

// SEGURANÇA: Requer que ptr aponte para len bytes válidos e inicializados,

// e que permaneçam válidos por &#039;static. Chamador deve garantir.

if ptr.is_null() {

return None;

}

Some(std::slice::from_raw_parts(ptr, len))

}

}</code></pre>

<p>Docummente as precondições. Use tipos que forçam segurança quando possível: <code>Option&lt;T&gt;</code>, <code>Result&lt;T, E&gt;</code>, tipos phantom para lifetime tracking. Teste com <code>cargo test</code> e ferramentas como Miri para detectar undefined behavior.</p>

<pre><code class="language-bash">cargo +nightly miri test</code></pre>

<p>Miri emula execução Rust e detecta muitos erros de unsafe que passariam despercebidos.</p>

<h2>Conclusão</h2>

<p>Unsafe Rust não é o vilão — é uma ferramenta específica para casos específicos. Lembre-se de três pontos críticos: <strong>(1)</strong> Use <code>unsafe</code> apenas quando absolutamente necessário e minimize seu escopo; <strong>(2)</strong> Documente rigorosamente as precondições e invariantes; <strong>(3)</strong> Encapsule unsafe em APIs seguras quando possível, deixando complexidade para dentro.</p>

<p>O compilador Rust protege você 99% do tempo. Quando você entra em territorio unsafe, você assume esse 1% restante. Use essa responsabilidade com sabedoria.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html" target="_blank" rel="noopener noreferrer">The Rust Book — Unsafe Rust</a></li>

<li><a href="https://doc.rust-lang.org/nomicon/" target="_blank" rel="noopener noreferrer">The Rustonomicon — Unsafe Code</a></li>

<li><a href="https://github.com/rust-lang/miri" target="_blank" rel="noopener noreferrer">Miri Interpreter for Undefined Behavior Detection</a></li>

<li><a href="https://rust-lang.github.io/api-guidelines/" target="_blank" rel="noopener noreferrer">Rust API Guidelines — Safety</a></li>

<li><a href="https://www.youtube.com/watch?v=QAz-maaH0KE" target="_blank" rel="noopener noreferrer">Jon Gjengset — Crust of Rust: Unsafe Code</a></li>

</ul>

Comentários

Mais em Rust

Guia Completo de Iteradores Customizados: Implementando o Trait Iterator
Guia Completo de Iteradores Customizados: Implementando o Trait Iterator

Por que Implementar Iteradores Customizados? Iteradores são fundamentais em l...

Boas Práticas de FFI em Rust: Interoperabilidade com C e Outras Linguagens para Times Ágeis
Boas Práticas de FFI em Rust: Interoperabilidade com C e Outras Linguagens para Times Ágeis

FFI em Rust: Interoperabilidade com C e Outras Linguagens FFI (Foreign Functi...

RefCell<T> e Interior Mutability em Rust: Do Básico ao Avançado
RefCell<T> e Interior Mutability em Rust: Do Básico ao Avançado

O que é Interior Mutability? Interior mutability é um padrão de design em Rus...