Entrada de datos de usuario

Una forma fácil de permitir que el usuario "teclee" información para el programa es usar la librería std::io::stdin. Esta es la librería "estándar de entrada", que recibe las entradas del teclado. Con stdin() se puede obtener la entrada del usuario. Esto se guardará en una &mut String con .read_line(). A continuación se muestra un ejemplo simple que no funciona del todo:

use std::io;

fn main() {
    println!("Por favor, teclea algo o x para terminar:");
    let mut input_string = String::new();

    while input_string != "x" { // Esto es lo que no funciona bien
        input_string.clear(); // Primero vacía la cadena de caracteres. En otro caso, se estaría añadiendo sin parar información
        io::stdin().read_line(&mut input_string).unwrap(); // Obtiene la entrada del usuario y la pone en  read_string
        println!("Escribiste: {}", input_string);
    }
    println!("¡Adios!");
}

Esta es es la salida de este programa:

Por favor, teclea algo o x para terminar:
algo
Escribiste: algo

Algo más
Escribiste: Algo más

x
Escribiste: x

x
Escribiste: x

x
Escribiste: x

Toma el valor de entrada y lo escribe en pantalla, pero continúa a pesar de haber escrito x. La única forma de salir es cerrar la ventana o pulsar ctrl y c simultáneamente. Si se modifica el {} del println! por {:?} para obtener más información (o se usa dbg!(&input_string)) el programa imprime lo siguiente:

Por favor, teclea algo o x para terminar:
algo
Escribiste: "algo\r\n"
algo más
Escribiste: "algo más\r\n"
x
Escribiste: "x\r\n"
x
Escribiste: "x\r\n"

Esto se debe a que la entrada que se recibe del teclado no es la palabra "algo", sino que es "algo" y el resultado de la tecla Intro (Enter en inglés). La forma más fácil para resolver esto es usar el método .trim() que elimina todos los espacios en blanco que rodean al texto por ambos lados. En esta caso por espacio en blanco .trim() entiende estos caracteres:

U+0009 (tabulador horizontal, '\t')
U+000A (salto de línea, '\n')
U+000B (tabulador vertical)
U+000C (salto de página)
U+000D (retorno de carro, '\r')
U+0020 (espacio, ' ')
U+0085 (siguiente línea)
U+200E (marca de izquierda a derecha)
U+200F (marca de derecha a izquierda)
U+2028 (separador de línea)
U+2029 (separador de párrafo)

De esta forma, la cadena x\r\n se convierte en x. Con este cambio, la aplicación funciona:

use std::io;

fn main() {
    println!("Por favor, teclea algo o x para terminar:");
    let mut input_string = String::new();

    while input_string.trim() != "x" {
        input_string.clear();
        io::stdin().read_line(&mut input_string).unwrap();
        println!("Escribiste: {}", input_string);
    }
    println!("¡Adiós!");
}

Ahora imprimirá:

Por favor, teclea algo o x para terminar:
algo
Escribiste: algo

algo
Escribiste: algo

x
Escribiste: x

¡Adiós!

Hay otra clase de entrada de usuario denominada std::env::Args (env significa entorno). Args es lo que teclea el usuario cuando inicia el programa (en la misma línea de arranque). Siempre existe un Arg en todos los programas.

fn main() {
    println!("{:?}", std::env::args());
}

Si se teclea cargo run el código anterior imprime:

Args { inner: ["target\\debug\\rust_book.exe"] }

A continuación, se ejecuta el mismo programa, pero pasándole más argumentos, por ejemplo: cargo run but with some extra words. En este caso, imprime:

Args { inner: ["target\\debug\\rust_book.exe", "but", "with", "some", "extra", "words"] }

Es interesante el resultado. Cuando se observa la página de Args se ve que implementa IntoIterator. Esto significa que se puede leer con un iterador:

use std::env::args;

fn main() {
    let input = args();

    for entry in input {
        println!("You entered: {}", entry);
    }
}

Que imprime:

You entered: target\debug\rust_book.exe
You entered: but
You entered: with
You entered: some
You entered: extra
You entered: words

El primer argumento siempre es el nombre del programa, por lo que lo habitual es saltárselo:

use std::env::args;

fn main() {
    let input = args();

    input.skip(1).for_each(|item| {
        println!("You wrote {}, which in capital letters is {}", item, item.to_uppercase());
    })
}

Que imprimirá:

You wrote but, which in capital letters is BUT
You wrote with, which in capital letters is WITH
You wrote some, which in capital letters is SOME
You wrote extra, which in capital letters is EXTRA
You wrote words, which in capital letters is WORDS

Un uso habitual de Args es para las configuraciones de usuario. A continuación, se muestra un programa que pone las palabas que se teclean en mayúsculas (capital) o minúsculas (lowercase), según sea la configuración inicial:

use std::env::args;

enum Letters {
    Capitalize,
    Lowercase,
    Nothing,
}

fn main() {
    let mut changes = Letters::Nothing;
    let input = args().collect::<Vec<_>>();

    if input.len() > 2 {
        match input[1].as_str() {
            "capital" => changes = Letters::Capitalize,
            "lowercase" => changes = Letters::Lowercase,
            _ => {}
        }
    }

    for word in input.iter().skip(2) {
      match changes {
        Letters::Capitalize => println!("{}", word.to_uppercase()),
        Letters::Lowercase => println!("{}", word.to_lowercase()),
        _ => println!("{}", word)
      }
    }
    
}

Algunos ejemplos de su ejecución:

Entrada cargo run please make capitals:

make capitals

Entrada cargo run capital:

// No imprime nada...

Entrada cargo run capital I think I understand now:

I
THINK
I
UNDERSTAND
NOW

ENtrada cargo run lowercase Does this work too?:

does
this
work
too?

Además de los argumentos tecleados por el usuario al arrancar el programa, std::env tiene también Vars que son las variables del sistema. Estas son diversas configuraciones que el usuario no tiene que teclera. Para acceder a ellas se usa std::env::vars() que devuelve una lista de tuplas (String, String). Suelen existir muchas. POr ejemplo:

fn main() {
    for item in std::env::vars() {
        println!("{:?}", item);
    }
}

El programa anterior muestra la información completa de las variables de sesión del usaurio:

("CARGO", "/playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/cargo")
("CARGO_HOME", "/playground/.cargo")
("CARGO_MANIFEST_DIR", "/playground")
("CARGO_PKG_AUTHORS", "The Rust Playground")
("CARGO_PKG_DESCRIPTION", "")
("CARGO_PKG_HOMEPAGE", "")
("CARGO_PKG_NAME", "playground")
("CARGO_PKG_REPOSITORY", "")
("CARGO_PKG_VERSION", "0.0.1")
("CARGO_PKG_VERSION_MAJOR", "0")
("CARGO_PKG_VERSION_MINOR", "0")
("CARGO_PKG_VERSION_PATCH", "1")
("CARGO_PKG_VERSION_PRE", "")
("DEBIAN_FRONTEND", "noninteractive")
("HOME", "/playground")
("HOSTNAME", "f94c15b8134b")
("LD_LIBRARY_PATH", "/playground/target/debug/build/backtrace-sys-3ec4c973f371c302/out:/playground/target/debug/build/libsqlite3-sys-fbddfbb9b241dacb/out:/playground/target/debug/build/ring-cadba5e583648abb/out:/playground/target/debug/deps:/playground/target/debug:/playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib:/playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib")
("PATH", "/playground/.cargo/bin:/playground/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
("PLAYGROUND_EDITION", "2018")
("PLAYGROUND_TIMEOUT", "10")
("PWD", "/playground")
("RUSTUP_HOME", "/playground/.rustup")
("RUSTUP_TOOLCHAIN", "stable-x86_64-unknown-linux-gnu")
("RUST_RECURSION_COUNT", "1")
("SHLVL", "1")
("SSL_CERT_DIR", "/usr/lib/ssl/certs")
("SSL_CERT_FILE", "/usr/lib/ssl/certs/ca-certificates.crt")
("USER", "playground")
("_", "/usr/bin/timeout")

La forma más sencilla de acceder a una de ellas de forma independiente es usar la macro env!. Se le da el nombre de una variable y devolverá un &str con el valor. No funcionará si la variable no existe, por lo que es mejor usar option_env!. Si se escribe el siguiente código en Playground:

fn main() {
    println!("{}", env!("USER"));
    println!("{}", option_env!("ROOT").unwrap_or("Can't find ROOT"));
    println!("{}", option_env!("CARGO").unwrap_or("Can't find CARGO"));
}

Se obtiene:

playground
Can't find ROOT
/playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/bin/cargo

Se ve que es mejor usar option_env!, salvo que realmente se quiera que el programa falle si no existe determinada variable de entorno de sistema/usuario, entonces sería mejor env!.