---
title: "Path e PathBuf em Rust"
url: "https://rustlang.com.br/stdlib/path/"
markdown_url: "https://rustlang.com.br/stdlib/path.MD"
description: "Referência completa de Path e PathBuf em Rust: criar caminhos, join, parent, extension, exists, canonicalize e caminhos cross-platform."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Path e PathBuf em Rust

Referência completa de Path e PathBuf em Rust: criar caminhos, join, parent, extension, exists, canonicalize e caminhos cross-platform.


# Path e PathBuf em Rust

`Path` e `PathBuf` são os tipos do Rust para representar caminhos no sistema de arquivos de forma segura e portável. `Path` é uma referência imutável (similar a `&str`), enquanto `PathBuf` é a versão com propriedade (owned, similar a `String`). Esses tipos tratam automaticamente as diferenças entre separadores de diretório do Windows (`\`) e do Unix (`/`), e oferecem métodos ricos para manipulação de componentes do caminho.

## Visão geral e tipos-chave

### Path vs PathBuf

A relação entre `Path` e `PathBuf` segue o mesmo padrão de `str`/`String` e `[T]`/`Vec<T>`:

| Aspecto | `Path` | `PathBuf` |
|---|---|---|
| Propriedade | Emprestado (`&Path`) | Com propriedade (owned) |
| Mutável | Não | Sim |
| Análogo a | `&str`, `&[T]` | `String`, `Vec<T>` |
| Criação | `Path::new("caminho")` | `PathBuf::from("caminho")` |

### Componentes internos

Um caminho é composto por:
- **Prefixo** (Windows): `C:`, `\\servidor\compartilhamento`
- **Raiz**: `/` (Unix) ou `\` (Windows)
- **Componentes normais**: nomes de diretórios e arquivo
- **Nome do arquivo**: último componente
- **Extensão**: parte após o último `.` no nome

## Padrões comuns com código

### Criando caminhos

```rust
use std::path::{Path, PathBuf};

fn main() {
    // Path — referência imutável
    let caminho = Path::new("/home/usuario/documentos/relatorio.pdf");
    println!("Path: {}", caminho.display());

    // PathBuf — com propriedade, mutável
    let mut buf = PathBuf::from("/home/usuario");
    buf.push("projetos");
    buf.push("rust");
    buf.push("src");
    buf.push("main.rs");
    println!("PathBuf: {}", buf.display());

    // Conversão entre tipos
    let referencia: &Path = buf.as_path();
    let owned: PathBuf = referencia.to_path_buf();

    // De String/&str
    let de_string = PathBuf::from(String::from("/tmp/arquivo.txt"));
    let de_str = Path::new("/tmp/arquivo.txt");
}
```

### Juntando caminhos com join

```rust
use std::path::{Path, PathBuf};

fn main() {
    let base = Path::new("/home/usuario");

    // join() cria um novo PathBuf
    let config = base.join("config").join("app.toml");
    println!("Config: {}", config.display());
    // /home/usuario/config/app.toml

    // join com caminho absoluto substitui completamente
    let absoluto = base.join("/etc/hosts");
    println!("Absoluto: {}", absoluto.display());
    // /etc/hosts (o caminho absoluto substitui a base)

    // Construindo caminhos dinamicamente
    let projeto = "meu_app";
    let arquivo = format!("{}.rs", "main");
    let caminho = Path::new("projetos").join(projeto).join("src").join(arquivo);
    println!("Projeto: {}", caminho.display());
    // projetos/meu_app/src/main.rs
}
```

### Extraindo componentes

```rust
use std::path::Path;

fn analisar_caminho(caminho: &str) {
    let p = Path::new(caminho);

    println!("Caminho: {}", p.display());
    println!("  parent():     {:?}", p.parent().map(|p| p.display().to_string()));
    println!("  file_name():  {:?}", p.file_name());
    println!("  file_stem():  {:?}", p.file_stem());
    println!("  extension():  {:?}", p.extension());

    println!("  Componentes:");
    for componente in p.components() {
        println!("    {:?}", componente);
    }
    println!();
}

fn main() {
    analisar_caminho("/home/usuario/projeto/src/main.rs");
    analisar_caminho("../dados/relatorio.csv.gz");
    analisar_caminho("/");
}
```

## Tabela de métodos principais

### Métodos de consulta (Path e PathBuf)

| Método | Retorno | Descrição |
|---|---|---|
| `parent()` | `Option<&Path>` | Diretório pai |
| `file_name()` | `Option<&OsStr>` | Nome do arquivo (último componente) |
| `file_stem()` | `Option<&OsStr>` | Nome sem extensão |
| `extension()` | `Option<&OsStr>` | Extensão do arquivo |
| `components()` | `Components` | Iterador sobre componentes |
| `ancestors()` | `Ancestors` | Iterador sobre caminhos ancestrais |
| `starts_with(prefix)` | `bool` | Verifica prefixo |
| `ends_with(suffix)` | `bool` | Verifica sufixo |
| `is_absolute()` | `bool` | Caminho é absoluto? |
| `is_relative()` | `bool` | Caminho é relativo? |
| `display()` | `Display` | Para exibição formatada |
| `to_str()` | `Option<&str>` | Converte para `&str` (pode falhar) |
| `to_string_lossy()` | `Cow<str>` | Converte para string (substitui chars inválidos) |

### Métodos do filesystem (Path)

| Método | Retorno | Descrição |
|---|---|---|
| `exists()` | `bool` | Arquivo/diretório existe? |
| `is_file()` | `bool` | É um arquivo regular? |
| `is_dir()` | `bool` | É um diretório? |
| `is_symlink()` | `bool` | É um link simbólico? |
| `metadata()` | `io::Result<Metadata>` | Metadados (segue symlinks) |
| `symlink_metadata()` | `io::Result<Metadata>` | Metadados (não segue symlinks) |
| `canonicalize()` | `io::Result<PathBuf>` | Caminho absoluto canônico |
| `read_dir()` | `io::Result<ReadDir>` | Lista entradas do diretório |
| `read_link()` | `io::Result<PathBuf>` | Destino do symlink |

### Métodos de mutação (PathBuf)

| Método | Descrição |
|---|---|
| `push(path)` | Adiciona componente ao final |
| `pop()` | Remove o último componente |
| `set_file_name(name)` | Substitui o nome do arquivo |
| `set_extension(ext)` | Substitui a extensão |
| `with_file_name(name)` | Retorna novo PathBuf com outro nome |
| `with_extension(ext)` | Retorna novo PathBuf com outra extensão |

## Exemplos práticos

### Exemplo 1: Renomear arquivos por extensão

```rust
use std::fs;
use std::io;
use std::path::Path;

fn renomear_extensao(diretorio: &str, de: &str, para: &str) -> io::Result<usize> {
    let mut contagem = 0;

    for entrada in fs::read_dir(diretorio)? {
        let entrada = entrada?;
        let caminho = entrada.path();

        if caminho.extension().and_then(|e| e.to_str()) == Some(de) {
            let novo = caminho.with_extension(para);
            fs::rename(&caminho, &novo)?;
            println!(
                "  {} -> {}",
                caminho.file_name().unwrap().to_string_lossy(),
                novo.file_name().unwrap().to_string_lossy()
            );
            contagem += 1;
        }
    }

    Ok(contagem)
}

fn main() -> io::Result<()> {
    let n = renomear_extensao("fotos", "jpeg", "jpg")?;
    println!("Renomeados {} arquivos", n);
    Ok(())
}
```

### Exemplo 2: Resolver caminhos relativos

```rust
use std::io;
use std::path::{Path, PathBuf};

fn resolver_caminho(base: &Path, relativo: &str) -> io::Result<PathBuf> {
    let combinado = base.join(relativo);

    // canonicalize resolve .., . e symlinks
    match combinado.canonicalize() {
        Ok(absoluto) => {
            println!(
                "Resolvido: '{}' -> '{}'",
                relativo,
                absoluto.display()
            );
            Ok(absoluto)
        }
        Err(e) => {
            eprintln!(
                "Não foi possível resolver '{}' a partir de '{}': {}",
                relativo,
                base.display(),
                e
            );
            Err(e)
        }
    }
}

fn main() -> io::Result<()> {
    let cwd = std::env::current_dir()?;
    println!("Diretório atual: {}", cwd.display());

    resolver_caminho(&cwd, "src/main.rs")?;
    resolver_caminho(&cwd, "../outro_projeto")?;

    Ok(())
}
```

### Exemplo 3: Construir caminhos cross-platform

```rust
use std::path::{Path, PathBuf, MAIN_SEPARATOR};

fn diretorio_config() -> PathBuf {
    // Caminho de configuração baseado no SO
    if cfg!(target_os = "windows") {
        // Windows: C:\Users\<user>\AppData\Roaming\MeuApp
        let appdata = std::env::var("APPDATA")
            .unwrap_or_else(|_| String::from("C:\\Users\\Public"));
        PathBuf::from(appdata).join("MeuApp")
    } else if cfg!(target_os = "macos") {
        // macOS: ~/Library/Application Support/MeuApp
        let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/tmp"));
        PathBuf::from(home)
            .join("Library")
            .join("Application Support")
            .join("MeuApp")
    } else {
        // Linux/outros: ~/.config/meuapp
        let home = std::env::var("HOME").unwrap_or_else(|_| String::from("/tmp"));
        PathBuf::from(home).join(".config").join("meuapp")
    }
}

fn main() {
    let config_dir = diretorio_config();
    println!("Diretório de config: {}", config_dir.display());
    println!("Separador do sistema: '{}'", MAIN_SEPARATOR);

    let config_file = config_dir.join("settings.toml");
    println!("Arquivo de config: {}", config_file.display());

    // Verificar existência
    if config_dir.exists() {
        println!("Diretório já existe");
    } else {
        println!("Diretório precisa ser criado");
    }
}
```

### Exemplo 4: Navegar pela hierarquia com ancestors

```rust
use std::path::Path;
use std::fs;
use std::io;

/// Busca um arquivo subindo na hierarquia de diretórios (como git busca .git)
fn buscar_acima(inicio: &Path, nome_arquivo: &str) -> io::Result<Option<std::path::PathBuf>> {
    let inicio_abs = if inicio.is_absolute() {
        inicio.to_path_buf()
    } else {
        std::env::current_dir()?.join(inicio)
    };

    for ancestral in inicio_abs.ancestors() {
        let candidato = ancestral.join(nome_arquivo);
        if candidato.exists() {
            return Ok(Some(candidato));
        }
    }

    Ok(None)
}

fn main() -> io::Result<()> {
    let cwd = std::env::current_dir()?;
    println!("Procurando Cargo.toml a partir de: {}", cwd.display());

    // ancestors() retorna todos os diretórios pai até a raiz
    println!("\nAncestors:");
    for anc in cwd.ancestors() {
        println!("  {}", anc.display());
    }

    match buscar_acima(&cwd, "Cargo.toml")? {
        Some(encontrado) => println!("\nEncontrado: {}", encontrado.display()),
        None => println!("\nCargo.toml não encontrado na hierarquia"),
    }

    Ok(())
}
```

### Exemplo 5: Manipulação segura de extensões de arquivo

```rust
use std::path::{Path, PathBuf};

fn adicionar_sufixo_antes_extensao(caminho: &Path, sufixo: &str) -> PathBuf {
    let stem = caminho
        .file_stem()
        .unwrap_or_default()
        .to_string_lossy();
    let extensao = caminho.extension().and_then(|e| e.to_str());

    let novo_nome = match extensao {
        Some(ext) => format!("{}{}.{}", stem, sufixo, ext),
        None => format!("{}{}", stem, sufixo),
    };

    match caminho.parent() {
        Some(pai) => pai.join(novo_nome),
        None => PathBuf::from(novo_nome),
    }
}

fn gerar_nomes_variantes(caminho: &str) -> Vec<PathBuf> {
    let p = Path::new(caminho);
    vec![
        adicionar_sufixo_antes_extensao(p, "_backup"),
        adicionar_sufixo_antes_extensao(p, "_v2"),
        p.with_extension("bak"),
        p.with_extension("tmp"),
    ]
}

fn main() {
    let variantes = gerar_nomes_variantes("/dados/relatorio.xlsx");
    println!("Variantes de '/dados/relatorio.xlsx':");
    for v in &variantes {
        println!("  {}", v.display());
    }
    // /dados/relatorio_backup.xlsx
    // /dados/relatorio_v2.xlsx
    // /dados/relatorio.bak
    // /dados/relatorio.tmp

    // Lidar com arquivos sem extensão
    let variantes2 = gerar_nomes_variantes("/bin/executavel");
    println!("\nVariantes de '/bin/executavel':");
    for v in &variantes2 {
        println!("  {}", v.display());
    }
}
```

## Caminhos e OsStr: lidando com Unicode

Nem todos os caminhos são UTF-8 válido. Por isso, `Path` trabalha com `OsStr`/`OsString` internamente:

```rust
use std::ffi::OsStr;
use std::path::Path;

fn processar_nome_seguro(caminho: &Path) -> String {
    // to_str() pode falhar se o caminho não for UTF-8 válido
    match caminho.file_name().and_then(|n| n.to_str()) {
        Some(nome) => nome.to_string(),
        None => {
            // to_string_lossy() substitui caracteres inválidos com U+FFFD
            caminho
                .file_name()
                .map(|n| n.to_string_lossy().into_owned())
                .unwrap_or_else(|| String::from("<desconhecido>"))
        }
    }
}

fn main() {
    let caminho = Path::new("/home/usuario/relatório.pdf");
    println!("Nome seguro: {}", processar_nome_seguro(caminho));
}
```

## Dicas de desempenho

- **Use `&Path` em parâmetros de função** em vez de `&PathBuf` — é mais genérico e evita cópias desnecessárias (assim como `&str` vs `&String`).
- **`exists()`, `is_file()`, `is_dir()` fazem syscalls** — cache o resultado se for chamar múltiplas vezes para o mesmo caminho.
- **`canonicalize()` é custoso** — resolve symlinks e acessa o disco. Use apenas quando precisar do caminho absoluto real.
- **`join()` aloca** um novo `PathBuf` — se estiver construindo caminhos em loop, considere reutilizar um `PathBuf` com `push()`/`pop()`.

## Veja também

- [Módulo std::fs](/stdlib/fs-module/) — operações no sistema de arquivos
- [File e OpenOptions](/stdlib/file/) — abrir arquivos usando caminhos
- [Módulo std::env](/stdlib/env-module/) — diretório atual, diretório temporário
- [Ler Arquivo em Rust](/receitas/ler-arquivo/) — receita que usa Path na prática
- Documentação oficial: [`std::path::Path`](https://doc.rust-lang.org/std/path/struct.Path.html), [`std::path::PathBuf`](https://doc.rust-lang.org/std/path/struct.PathBuf.html)
