---
title: "Como Executar uma Função em Outra Thread em Rust"
url: "https://rustlang.com.br/blog/executar-funcao-outra-thread-rust/"
markdown_url: "https://rustlang.com.br/blog/executar-funcao-outra-thread-rust.MD"
description: "Aprenda a executar uma função em outra thread em Rust: passe argumentos com move, receba resultados com join e trate erros sem perder o controle da execução."
date: "2026-10-06"
author: "Equipe Rust Brasil"
---

# Como Executar uma Função em Outra Thread em Rust

Aprenda a executar uma função em outra thread em Rust: passe argumentos com move, receba resultados com join e trate erros sem perder o controle da execução.


**Para executar uma função em outra thread em Rust, use `std::thread::spawn`.** Sem argumentos, passe a própria função: `thread::spawn(calcular)`. Com argumentos, passe uma closure: `thread::spawn(move || calcular(dados))`. Guarde o `JoinHandle` e chame `join()` para esperar a thread terminar e receber seu resultado.

Esse padrão não exige Tokio nem uma crate externa. É útil em programas de terminal, ferramentas de processamento e integrações com operações bloqueantes. Neste tutorial, você vai separar a função que faz o trabalho da parte que controla sua execução, sem recorrer a dados globais ou a pausas artificiais com `sleep`.

## Resposta rápida: qual forma usar?

| Necessidade | Forma indicada | Observação |
|---|---|---|
| Chamar uma função sem argumentos | `thread::spawn(minha_funcao)` | Passe a função, não seu resultado |
| Enviar um `String` ou `Vec` para o worker | `thread::spawn(move || minha_funcao(dados))` | A thread passa a possuir os dados |
| Obter o valor retornado | `handle.join()` | Bloqueia a thread chamadora até a conclusão |
| Emprestar dados locais sem cloná-los | `thread::scope` e `scope.spawn` | Todas as threads terminam antes da saída do escopo |
| Compartilhar estado mutável | `Arc<Mutex<T>>`, quando necessário | Não é obrigatório para apenas enviar argumentos |
| Processar muitos itens CPU-bound | Um pool, como Rayon | Evite uma thread nativa por item |
| Executar trabalho bloqueante em Tokio | `tokio::task::spawn_blocking` | Controle também o número de trabalhos concorrentes |

Para consultar outras operações do módulo, veja a referência de [std::thread](/stdlib/thread/). Aqui, o foco é o fluxo **função → argumentos → resultado → erro**.

## 1. Passe a função sem executá-la antes

Crie um projeto com [Cargo](/instalacao/cargo/):

```bash
cargo new funcao-em-thread
cd funcao-em-thread
```

Substitua `src/main.rs` por este programa completo:

```rust
use std::thread;

fn calcular() -> u64 {
    (1_u64..=100_000).sum()
}

fn main() {
    let handle = thread::spawn(calcular);

    println!("A thread principal pode fazer outro trabalho.");

    let total = handle.join().expect("A thread de cálculo entrou em pânico");
    assert_eq!(total, 5_000_050_000);
    println!("Resultado: {total}");
}
```

Execute com `cargo run`. O resultado numérico será `5000050000`; a mensagem da thread principal não demonstra, por si só, que houve ganho de desempenho. A soma é pequena e o custo de criar a thread pode superar qualquer benefício.

A diferença entre `calcular` e `calcular()` é essencial. O primeiro é a função que será chamada pela nova thread. O segundo chama a função imediatamente na thread atual. Portanto, `thread::spawn(calcular())` tenta passar um `u64` onde a API espera algo que possa chamar, e não compila.

O `spawn` retorna antes de o trabalho necessariamente terminar. O sistema operacional decide quando cada thread roda; não conte com uma ordem específica de mensagens entre elas.

## 2. Use uma closure com move para passar argumentos

`thread::spawn` não recebe uma lista adicional de parâmetros para sua função. Ele recebe uma função ou closure sem argumentos. A closure resolve essa diferença ao capturar os valores necessários:

```rust
use std::thread;

fn contar_palavras(texto: String) -> usize {
    texto.split_whitespace().count()
}

fn main() {
    let texto = String::from("Rust executa funções em outra thread");
    let handle = thread::spawn(move || contar_palavras(texto));

    let quantidade = handle.join().expect("A contagem entrou em pânico");
    assert_eq!(quantidade, 6);
    println!("Palavras: {quantidade}");
}
```

`move` faz a closure capturar `texto` por valor. Como `String` não implementa `Copy`, sua posse é transferida; você não pode usar essa mesma variável na thread principal depois do `spawn`.

Se a thread principal ainda precisar do texto, escolha conscientemente entre clonar, compartilhar uma estrutura adequada ou emprestar com um escopo. Clonar pode ser suficiente para uma entrada pequena. Não coloque um `Mutex` em tudo apenas para satisfazer o compilador: neste exemplo, não existe estado mutável compartilhado que precise de um lock.

### Por que aparecem Send e 'static?

A closure e seu retorno precisam satisfazer `Send`, pois atravessam a fronteira entre threads. O requisito `'static` de `thread::spawn` impede que o trabalho carregue empréstimos locais que possam expirar antes de ele terminar.

Isso **não significa que um `String` enviado à thread precisa permanecer vivo para sempre**. Um valor possuído pode satisfazer o requisito sem conter referências de curta duração. Já `move` sobre `&String` só move a referência; não estende seu lifetime.

Para aprofundar essas regras, consulte [Send e Sync](/stdlib/send-sync/) e o [tutorial de concorrência](/tutoriais/concorrencia/).

## 3. Separe erro da função e panic da thread

Se a função retorna `Result<T, E>`, `join()` produz duas camadas de resultado:

- a camada externa informa se a thread terminou normalmente ou entrou em panic;
- a camada interna informa se a operação da aplicação teve sucesso ou falhou.

Este exemplo recebe um caminho, lê um arquivo em outra thread e trata os dois casos separadamente:

```rust
use std::{env, fs, io, process, thread};

fn contar_linhas(caminho: String) -> io::Result<usize> {
    let conteudo = fs::read_to_string(caminho)?;
    Ok(conteudo.lines().count())
}

fn main() {
    let Some(caminho) = env::args().nth(1) else {
        eprintln!("Uso: cargo run -- caminho/do/arquivo.txt");
        process::exit(2);
    };

    let handle = thread::spawn(move || contar_linhas(caminho));

    match handle.join() {
        Ok(Ok(linhas)) => println!("Linhas: {linhas}"),
        Ok(Err(erro)) => {
            eprintln!("Não foi possível ler o arquivo: {erro}");
            process::exit(1);
        }
        Err(_) => {
            eprintln!("A thread entrou em pânico");
            process::exit(1);
        }
    }
}
```

Para testar, crie `entrada.txt` com duas linhas e execute:

```bash
cargo run -- entrada.txt
cargo run -- arquivo-inexistente.txt
```

A segunda execução deve reportar o erro de leitura e sair com código `1`. Um arquivo ausente é uma falha esperada da operação, não um motivo para chamar `panic!`.

`join().expect(...)` é conveniente nos primeiros exemplos, mas não substitui uma política de tratamento de falhas. Com a estratégia de panic baseada em *unwinding*, `join` permite observar o panic do worker. Se o binário usa `panic = "abort"`, um panic encerra o processo, e essa recuperação não acontece. Veja também o guia de [tratamento de erros com thiserror e anyhow](/blog/tratamento-erros-rust-thiserror-anyhow/).

## 4. Quando usar thread::scope em vez de clone

Se o trabalho precisa apenas ler dados locais e terminar antes de a função chamadora continuar, um escopo permite emprestá-los diretamente. O programa abaixo mantém a posse do vetor na thread principal:

```rust
use std::thread;

fn somar(dados: &[u64]) -> u64 {
    dados.iter().sum()
}

fn main() {
    let dados = vec![10_u64, 20, 30, 40];

    let total = thread::scope(|scope| {
        let handle = scope.spawn(|| somar(&dados));
        handle.join().expect("A soma entrou em pânico")
    });

    assert_eq!(total, 100);
    println!("Total: {total}; vetor preservado: {dados:?}");
}
```

`thread::scope` aguarda todas as threads criadas nele antes de retornar. Esse limite de duração torna o empréstimo seguro. A API está disponível no Rust estável desde a versão 1.63.

O escopo não torna qualquer acesso compartilhado permitido: as regras de empréstimo e os requisitos de segurança entre threads continuam valendo. Para dividir um vetor mutável em regiões independentes ou criar vários workers, consulte [scoped threads](/stdlib/scoped-threads/).

## 5. Join não é cancelamento nem timeout

`join()` espera a thread acabar e consome o handle. Ele não recebe um prazo máximo e não interrompe o trabalho.

Descartar o `JoinHandle` também não cancela a execução: a thread fica desanexada. Se `main` terminar e o processo sair, o trabalho restante não tem garantia de conclusão. É por isso que usar `sleep` na thread principal para “dar tempo” ao worker é frágil; duração estimada não é sincronização.

Para tarefas longas, implemente cancelamento cooperativo: envie uma mensagem por um [canal](/stdlib/channels/) ou compartilhe um sinal que o worker consulta periodicamente. Um timeout ao esperar uma mensagem informa que a espera expirou; não mata automaticamente o worker. Ele ainda precisa obedecer ao protocolo de parada.

## Threads, Rayon ou Tokio: como escolher?

Uma thread nativa é uma escolha simples para um worker dedicado ou uma operação bloqueante isolada. Não crie uma thread por registro de um arquivo ou por requisição de uma API sem limite: cada uma exige recursos, incluindo uma pilha cujo tamanho depende da configuração e da plataforma.

Para paralelismo de dados, como transformar uma grande coleção, [Rayon](/blog/rayon-paralelismo-dados-rust/) oferece um pool e operações como `par_iter`. Meça o trabalho serial e o paralelo; o número de threads não é uma promessa de aceleração.

Em aplicações [Tokio](/ecossistema/tokio/), uma função `async` não deve executar leitura síncrona pesada ou cálculo demorado diretamente nas threads do executor. `spawn_blocking` atende operações bloqueantes, mas um volume alto de cálculos CPU-bound ainda exige limites de concorrência ou um pool apropriado. `tokio::spawn` agenda uma tarefa assíncrona; não significa criar uma thread nativa dedicada.

## Checklist antes de usar o padrão no projeto

- A função foi passada como valor ou chamada dentro de uma closure?
- Os argumentos foram movidos, emprestados por um escopo ou compartilhados deliberadamente?
- A thread principal mantém o handle e observa o resultado?
- Erros esperados da operação são distintos de panics?
- O programa espera os workers antes de encerrar?
- Existe um limite para a quantidade de trabalho concorrente?
- A medição justifica a complexidade adicional?

Como exercício de portfólio, adapte o leitor de arquivo para aceitar vários caminhos, mantendo um limite de workers e registrando falhas por arquivo. Essa evolução demonstra domínio de ownership, concorrência e tratamento de erros — competências úteis ao avaliar [vagas Rust](/vagas/). Para construir essa base com uma sequência de estudos, veja o [curso de Rust](/curso/).

## Perguntas frequentes

### Preciso de Tokio para chamar uma função em outra thread?

Não. Os exemplos deste tutorial usam apenas a biblioteca padrão. Tokio é indicado quando você precisa de um runtime assíncrono e de seu ecossistema de I/O, não como requisito para `std::thread::spawn`.

### Posso passar uma função que recebe argumentos diretamente?

Não se sua assinatura exige argumentos. `spawn` espera algo chamável sem parâmetros. Envolva a chamada em `move || minha_funcao(argumentos)` ou use uma closure com empréstimos dentro de `thread::scope`.

### Como recebo o retorno da função?

Chame `join()` no handle. O `Ok` externo contém o retorno da função. Se ela retorna um `Result`, trate também essa camada interna, como no exemplo de leitura de arquivo.

### move resolve qualquer erro de lifetime?

Não. Mover uma referência não prolonga a vida dos dados referenciados. Envie dados possuídos, use compartilhamento apropriado ou escolha `thread::scope` para empréstimos locais.

### Posso deixar a thread rodando depois de main?

Não conte com isso: o encerramento do processo termina as threads restantes. Aguarde os trabalhos que precisam concluir antes da saída, com `join` ou outro protocolo explícito de sincronização.

## Referências

- [Documentação oficial de thread::spawn](https://doc.rust-lang.org/std/thread/fn.spawn.html)
- [Documentação oficial de JoinHandle::join](https://doc.rust-lang.org/std/thread/struct.JoinHandle.html#method.join)
- [Documentação oficial de thread::scope](https://doc.rust-lang.org/std/thread/fn.scope.html)
