AkiraAkira.dev
3 min de leitura

Construí o onyx para aprender, não para competir

A mesma superfície de módulos em Go e Rust. paths, files, shell, clipboard, notify, keyring. Duas crates que existem porque as escrevi.

também em EN FR

Esta semana abri o código de duas crates. Um módulo Go, uma crate Rust. Mesmo nome, mesma superfície: osinfo, paths, files, shell, clipboard, notify, keyring. O conjunto chama-se onyx. Código em github.com/akira-io/onyx (Go) e github.com/akira-io/onyx-rs (Rust).

Não o construí para competir com nada.

O onyx é um projeto de estudo que deixei escapar para MIT. O ponto era a forma da cola cross-platform de desktop, não o mercado. dirs, arboard, notify-rust, keyring-rs. Existem todas. Funcionam todas. Ler as bibliotecas dos outros ensina-te como eles pensam. Escrever a tua ensina-te no que acreditas.

O problema

Cada aplicação de desktop que escrevi reescreve os mesmos shims de SO:

switch runtime.GOOS {
case "darwin":
    return filepath.Join(home, "Library", "Application Support", app), nil
case "linux":
    if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" {
        return filepath.Join(xdg, app), nil
    }
    return filepath.Join(home, ".config", app), nil
case "windows":
    if v := os.Getenv("APPDATA"); v != "" {
        return filepath.Join(v, app), nil
    }
    return filepath.Join(home, "AppData", "Roaming", app), nil
}

Esse bloco vive em cada app de desktop que entreguei. O custo não são as linhas de código. O custo é o imposto cognitivo de confirmar, outra vez, que SO o Linux significa e que caminho o Windows quer esta semana.

A regra

O onyx substitui esse bloco por uma linha.

// github.com/akira-io/onyx/paths
config, err := paths.For("Hyperion").Config()

Uma função. Um verbo. Uma forma de retorno. O Resolver segue a mesma disciplina.

// github.com/akira-io/onyx/shell
claude, err := shell.NewResolver().
    Lookup("claude").
    Lookup("/opt/homebrew/bin/claude").
    Resolve()

Lookup aceita ou um nome resolvido via PATH ou um caminho absoluto verificado como ficheiro. O package descobre qual porque a diferença é mecânica: se a string tem um separador, é um caminho. Quem chama não escolhe.

Não acho que esta seja a melhor API. Acho que é a API à qual vou pegar, o que é uma afirmação diferente e mais útil.

Porquê o espelho em Rust

O onyx-rs é o mesmo exercício numa segunda linguagem. Mesmos nomes de módulos, mesmas convenções, idiomático em cada uma. paths.For("Hyperion").Config() do lado Go torna-se paths::for_app("Hyperion").config() do lado Rust. shell.NewResolver() torna-se shell::Resolver::new(). O módulo Go devolve (string, error). A crate Rust devolve Result<PathBuf, ShellError>.

O ponto do port em Rust não é portabilidade para os utilizadores. É portabilidade para mim. Escrever as mesmas formas em Rust obrigou-me a olhar para cada default que não tinha reparado na versão Go: variantes de erro, ergonomia de builder, tipos de caminho, o que derivar, o que expor, o que manter privado à crate.

O contra-argumento óbvio

“Já há bibliotecas para isto.”

Sim. dirs, arboard, notify-rust, keyring-rs. Têm mais contribuidores, mais downloads, mais teste de batalha do que o onyx alguma vez terá. Se queres uma dependência em que te apoiar, usa-as.

O onyx existe pela mesma razão que leva as pessoas a reescrever o currículo em texto simples de dois em dois anos. Não porque o formato importa. Porque o ato de o escrever obriga-te a lembrar o que fazes.

Fecho

Abri o código porque entregar sob o teu nome é a única forma de manteres o código honesto. README público. Convenções públicas. Erros públicos. A crate que vive numa pasta privada de vendor para sempre não é um projeto de estudo. É um rascunho.

O onyx é um rascunho que me comprometi a entregar.

share
Caption copied — paste in the compose box