Rust

Equivalencias idiomáticas entre Kotlin y Rust. Kotlin se presenta como referencia en cada concepto.

Integración con el ecosistema

Un algoritmo correcto también debe compilarse, organizarse y ejecutarse: estas herramientas son las que hacen posible entregar y comparar una implementación real.

Equivalencia desde Kotlin

Herramientas

Cercana

En Rust, Cargo (Cargo.toml, cargo build, cargo test, cargo run) expresa la responsabilidad del fixture: Instala las dependencias declaradas, construye todos los módulos, ejecuta las pruebas y corre la aplicación de línea de comandos.

Referencia Kotlin: Gradle, mediante el wrapper ./gradlew, resuelve las dependencias declaradas, construye los subproyectos registrados en settings.gradle.kts y ejecuta tareas como build, test y :app:run.

Mecanismo
Cargo (Cargo.toml, cargo build, cargo test, cargo run)
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar Cargo (Cargo.toml, cargo build, cargo test, cargo run).
Recomendación
Expón el contrato del fixture y usa Cargo (Cargo.toml, cargo build, cargo test, cargo run) solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Distingue el ejecutable de build invocado (el wrapper o la CLI de la herramienta) de los archivos de manifiesto y lockfile que declaran las dependencias y su resolución.
  • La forma idiomática de Rust debe conservarse aunque la sintaxis difiera de Kotlin.

Instala las dependencias declaradas, construye todos los módulos, ejecuta las pruebas y corre la aplicación de línea de comandos.

docker compose run --rm app cargo build
docker compose run --rm app cargo test
docker compose run --rm app cargo run

Dockerfile mínimo para el toolchain de Rust.

FROM rust:1.83
WORKDIR /workspace
COPY . .

compose.yaml: construye la imagen y monta el proyecto para correr los comandos anteriores dentro del contenedor.

services:
  app:
    build: .
    volumes:
      - .:/workspace
    working_dir: /workspace

Fuentes

Equivalencia desde Kotlin

Módulos, workspaces y monorepos

Cercana

En Rust, un workspace de Cargo ([workspace] con members = [...] en el Cargo.toml raíz) expresa la responsabilidad del fixture: Organiza un ejecutable de línea de comandos que depende de módulos de dominio reutilizables, conservando el sentido de la dependencia.

Referencia Kotlin: Un build multi-proyecto de Gradle declara sus subproyectos (:app, :optimization:core, :optimization:execution, :optimization:baselines, :optimization:metaheuristics:single-state, :reporting:fair) en settings.gradle.kts mediante include(...), mientras que la lógica de build compartida vive en builds incluidos aparte (build-logic, build-conventions) declarados con includeBuild(...); cada dependencia entre proyectos se declara explícitamente, por ejemplo implementation(projects.optimization.core) en el build.gradle.kts de :app.

Mecanismo
un workspace de Cargo ([workspace] con members = [...] en el Cargo.toml raíz)
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar un workspace de Cargo ([workspace] con members = [...] en el Cargo.toml raíz).
Recomendación
Expón el contrato del fixture y usa un workspace de Cargo ([workspace] con members = [...] en el Cargo.toml raíz) solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Un monorepo es una estrategia de alojamiento de un repositorio, no un mecanismo de build: un repositorio puede ser un monorepo sin usar ningún workspace, y un workspace puede vivir en un repositorio que no aloja ningún otro proyecto además del suyo.
  • La forma idiomática de Rust debe conservarse aunque la sintaxis difiera de Kotlin.

Organiza un ejecutable de línea de comandos que depende de módulos de dominio reutilizables, conservando el sentido de la dependencia.

# Cargo.toml (raíz)
[workspace]
members = ["cli", "core"]

# cli/Cargo.toml
[dependencies]
core = { path = "../core" }

Fuentes

Equivalencia desde Kotlin

Soporte CLI

Cercana

En Rust, clap en su variante derive (#[derive(Parser)], #[command(subcommand)]) expresa la responsabilidad del fixture: Ejecuta random-search con un archivo de configuración y escribe su reporte, mostrando ayuda generada y fallando antes de optimizar ante una opción inválida.

Referencia Kotlin: Clikt define atom como comando raíz sin operación propia (NoOpCliktCommand) con los subcomandos random-search y local-search; cada uno extiende AbstractExperimentCommand, que declara --config y --report como opciones requeridas y --report-format con v4 por defecto, valida la configuración antes de ejecutar el experimento y traduce los errores esperados en CliktError.

Mecanismo
clap en su variante derive (#[derive(Parser)], #[command(subcommand)])
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar clap en su variante derive (#[derive(Parser)], #[command(subcommand)]).
Recomendación
Expón el contrato del fixture y usa clap en su variante derive (#[derive(Parser)], #[command(subcommand)]) solo para la garantía que realmente ofrece.

Diferencias relevantes

  • El análisis de argumentos y su validación deben completarse antes de ejecutar cualquier lógica de optimización; ninguna biblioteca madura y con pruebas propias debe reemplazarse por un analizador manual.
  • La forma idiomática de Rust debe conservarse aunque la sintaxis difiera de Kotlin.

Ejecuta random-search con un archivo de configuración y escribe su reporte, mostrando ayuda generada y fallando antes de optimizar ante una opción inválida.

#[derive(Parser)]
struct RandomSearchArgs {
    #[arg(long)]
    config: PathBuf,
    #[arg(long)]
    report: PathBuf,
    #[arg(long, default_value = "v4")]
    report_format: String,
}

Contrato observable compartido por cualquier reimplementación de la CLI.

$ atom random-search --config config.json --report report.json
Completed random-search: 2 runs; report format: v4; report: report.json

Fuentes

Equivalencia desde Kotlin

Serialización

Cercana

En Rust, serde con serde_json (#[derive(Deserialize)] y serde_json::from_str) expresa la responsabilidad del fixture: Decodifica una configuración JSON con el objetivo, las semillas y el valor objetivo opcional de un experimento, distinguiendo un JSON sintácticamente inválido de un valor de dominio inválido.

Referencia Kotlin: kotlinx.serialization decodifica el JSON tipado mediante un Json compartido (prettyPrint = true, encodeDefaults = true) y un SerializersModule polimórfico para tipos como SearchSpace; readFromJson<T>() decodifica el archivo y deja que SerializationException se propague, mientras que un valor de dominio inválido, como un Delta no positivo, falla por separado en el constructor de su clase de valor.

Mecanismo
serde con serde_json (#[derive(Deserialize)] y serde_json::from_str)
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar serde con serde_json (#[derive(Deserialize)] y serde_json::from_str).
Recomendación
Expón el contrato del fixture y usa serde con serde_json (#[derive(Deserialize)] y serde_json::from_str) solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Un JSON sintácticamente válido puede seguir violando un invariante de dominio; decodificar con éxito no implica que el valor resultante sea válido.
  • La forma idiomática de Rust debe conservarse aunque la sintaxis difiera de Kotlin.

Decodifica una configuración JSON con el objetivo, las semillas y el valor objetivo opcional de un experimento, distinguiendo un JSON sintácticamente inválido de un valor de dominio inválido.

#[derive(Deserialize)]
struct ExperimentConfig {
    objective: String,
    seeds: Vec<i32>,
    #[serde(default)]
    target_value: f64,
}

let config: ExperimentConfig = serde_json::from_str(json_text)?;

Payload compartido: seeds es requerido; targetValue es opcional y por defecto 0.0.

{
  "objective": "sphere",
  "seeds": [7, 11],
  "targetValue": 0.0
}

Fuentes

Ecosistemas de pruebas

Verificar el comportamiento de una implementación requiere elegir, entre varias estrategias, la que responde mejor a cada tipo de afirmación sobre el algoritmo.

Equivalencia desde Kotlin

Desarrollo guiado por pruebas (TDD)

Cercana

Formula primero el comportamiento con Given–When–Then. En rojo, ejecuta el test más pequeño que demuestra que falta; en verde, implementa solo lo necesario; al refactorizar, mejora el diseño sin cambiar el comportamiento. Repite el ciclo con la siguiente afirmación. BDD puede ayudar a descubrir o expresar el comportamiento, pero Gherkin y los nombres legibles no son requisitos de TDD.

Referencia Kotlin: TDD parte por una afirmación observable y usa el test para conducir un cambio pequeño.

Mecanismo
el harness de cargo test con assert!
Riesgo de diseño
Escribir una prueba grande que atraviesa configuración, red o archivos antes de haber fijado una sola regla observable.
Recomendación
Elige una afirmación pequeña, ejecútala en rojo, hazla verde con el cambio mínimo y refactoriza solo mientras permanezca verde.

Diferencias relevantes

  • El ciclo rojo–verde–refactor se conserva entre ecosistemas; cambian el runner, la sintaxis de aserciones y la forma de expresar una excepción.
  • El test debe fallar por el comportamiento ausente, no por una dependencia, una ruta o una integración todavía no necesaria.

Un presupuesto de evaluación positivo se acepta y uno no positivo se rechaza.

#[test]
fn rejects_a_non_positive_budget() {
    assert!(Budget::new(0).is_err());
}

Fuentes

Equivalencia desde Kotlin

Selección de estrategia de pruebas

Cercana

Empieza por la afirmación: usa un ejemplo enfocado para un comportamiento conocido, DDT para una matriz finita y PBT para una ley general. Añade pruebas de componente o integración cuando colaboran módulos, pruebas de contrato para una representación compartida y una verificación de entrega cuando importa el entorno documentado. Para algoritmos estocásticos controla colaboradores, conserva semillas reproducibles y prueba invariantes o evidencia estadística, no una trayectoria casual.

Referencia Kotlin: Seleccionar pruebas parte de la afirmación, la frontera afectada y las dependencias que deben controlarse.

Mecanismo
cargo test y módulos de prueba para evidencia unitaria e integrada
Riesgo de diseño
Usar una prueba end-to-end para demostrar una regla local, o adoptar PBT solo porque hay un generador disponible.
Recomendación
Elige la evidencia más pequeña que pueda observar y falsar la afirmación; amplíala solo cuando una frontera modificada lo requiera.

Diferencias relevantes

  • La selección depende de la afirmación y la frontera, no del lenguaje ni del tamaño de una función.
  • Una prueba más amplia complementa la evidencia enfocada de TDD; no la reemplaza.

La afirmación determina la evidencia más pequeña adecuada antes de ampliar la verificación.

// Regla conocida: un #[test].
assert!(Budget::new(0).is_err());
// Matriz finita: casos explícitos.
// Ley general: proptest!.

Fuentes

Equivalencia desde Kotlin

Pruebas unitarias

Cercana

En Rust, el harness integrado con #[test], assert_eq! y #[should_panic] expresa la responsabilidad del fixture: Acepta el presupuesto 1000 y rechaza el presupuesto -1 antes de ejecutar optimización.

Referencia Kotlin: Una prueba unitaria llama al analizador de presupuesto y compara el valor o el error de validación.

Mecanismo
el harness integrado con #[test], assert_eq! y #[should_panic]
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar el harness integrado con #[test], assert_eq! y #[should_panic].
Recomendación
Expón el contrato del fixture y usa el harness integrado con #[test], assert_eq! y #[should_panic] solo para la garantía que realmente ofrece.

Diferencias relevantes

  • La prueba mantiene el análisis local: no inicia una ejecución de optimización ni un proceso externo.
  • La forma idiomática de Rust debe conservarse aunque la sintaxis difiera de Kotlin.

Acepta el presupuesto 1000 y rechaza el presupuesto -1 antes de ejecutar optimización.

#[test]
fn rejects_negative_budget() {
    assert!(parse_budget("-1").is_err());
}

Fuentes

Equivalencia desde Kotlin

Frameworks y estilos BDD

Cercana

En Rust, cucumber-rs con features Gherkin y steps async expresa la responsabilidad del fixture: Describe que atom random-search --objective sphere --budget 1000 --seed 11 produce una configuración válida sin ejecutar optimización.

Referencia Kotlin: Un escenario describe la configuración observable; analizar el comando no ejecuta el algoritmo.

Mecanismo
cucumber-rs con features Gherkin y steps async
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar cucumber-rs con features Gherkin y steps async.
Recomendación
Expón el contrato del fixture y usa cucumber-rs con features Gherkin y steps async solo para la garantía que realmente ofrece.

Diferencias relevantes

  • BDD comunica comportamiento y colaboración; un nombre de prueba legible por sí solo no crea un flujo de especificación completo.
  • La forma idiomática de Rust debe conservarse aunque la sintaxis difiera de Kotlin.

Describe que atom random-search --objective sphere --budget 1000 --seed 11 produce una configuración válida sin ejecutar optimización.

#[then(expr = "a valid experiment configuration is produced")]
async fn valid_configuration(world: &mut World) {
    assert!(parse(&world.args).is_ok());
}

Fuentes

Equivalencia desde Kotlin

Soporte DDT

Cercana

En Rust, #[case] de rstest expresa la responsabilidad del fixture: Una tabla cubre presupuesto válido, faltante, negativo, objetivo desconocido, semilla repetida y opción desconocida.

Referencia Kotlin: Los casos explícitos vinculan argumentos CLI con un resultado de análisis esperado.

Mecanismo
#[case] de rstest
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar #[case] de rstest.
Recomendación
Expón el contrato del fixture y usa #[case] de rstest solo para la garantía que realmente ofrece.

Diferencias relevantes

  • DDT enumera ejemplos elegidos; no sustituye la generación de dominios ni una especificación de comportamiento.
  • La forma idiomática de Rust debe conservarse aunque la sintaxis difiera de Kotlin.

Una tabla cubre presupuesto válido, faltante, negativo, objetivo desconocido, semilla repetida y opción desconocida.

#[case("1000", true)]
#[case("-1", false)]
fn budget_cases(#[case] value: &str, #[case] valid: bool) {
    assert_eq!(parse_budget(value).is_ok(), valid);
}

Fuentes

Equivalencia desde Kotlin

Soporte PBT

Cercana

proptest genera entradas y persiste los casos que fallan para que se puedan volver a ejecutar; complementa al harness de cargo test en vez de reemplazarlo.

Referencia Kotlin: Una propiedad general se ejecuta sobre valores generados y comunica cómo repetir un contraejemplo.

Mecanismo
proptest y su macro proptest!
Riesgo de diseño
Usar valores generados sin una propiedad más general que el ejemplo de una sola entrada.
Recomendación
Mantenga la estrategia pequeña, guarde el contraejemplo y conserve una tabla DDT para los argumentos que deben ser visibles al lector.

Diferencias relevantes

  • El macro declara la estrategia generadora junto a la propiedad.
  • Un contraejemplo se reduce y queda disponible para reproducción mediante la configuración de proptest.

Para cualquier texto decimal positivo aceptado, el presupuesto resultante es positivo; los casos inválidos se reducen y se pueden reproducir.

proptest! {
  #[test]
  fn positive_budgets_remain_positive(budget in 1i32..) {
    prop_assert!(parse_budget(&budget.to_string()).unwrap() > 0);
  }
}

Fuentes

Equivalencia desde Kotlin

DSLs de aserciones

Cercana

En Rust, macros estándar assert_eq! y assert! expresa la responsabilidad del fixture: Una configuración analizada contiene objective = sphere, budget = 1000 y seeds = [11]; una falla debe identificar esperado y recibido.

Referencia Kotlin: Las aserciones expresan el resultado observable de una configuración sin ocultar su estructura relevante.

Mecanismo
macros estándar assert_eq! y assert!
Riesgo de diseño
Copiar la sintaxis de Kotlin en vez de usar macros estándar assert_eq! y assert!.
Recomendación
Expón el contrato del fixture y usa macros estándar assert_eq! y assert! solo para la garantía que realmente ofrece.

Diferencias relevantes

  • Una biblioteca fluida puede mejorar el diagnóstico, pero no reemplaza una expectativa precisa ni es necesaria cuando la API idiomática ya basta.
  • La forma idiomática de Rust debe conservarse aunque la sintaxis difiera de Kotlin.

Una configuración analizada contiene objective = sphere, budget = 1000 y seeds = [11]; una falla debe identificar esperado y recibido.

assert_eq!(config.objective, "sphere");
assert_eq!(config.budget, 1000);
assert_eq!(config.seeds, vec![11]);

Fuentes