I am not a developer, but every few years I get the itch to tinker off-an-on for a few months at a time, and this round I’ve chosen Rust as my play-toy.
I’ve worked through the first couple or so chapters of “The Book”, but I’m the type that likes to get a general idea of a system and then to start digging in, scratching my own itches instead of following the instructor’s lead (I’ve accordingly never been a very good student).
I’ve worked the first four or five challenges of the 2020 “Advent of Code” project (https://adventofcode.com/), but then got the itch to to see how far I could get in rewriting the “sl” app found in Debian (written in C). “sl” is a “joke” app for when someone mistypes “ls” at the command-line, and it originally caused an ASCII choo-choo train to travel across the screen (more recent mods have added variations).
The C version uses the ncurses library, and so the first thing I had to do was figure out how to access the ncurses library from Rust; it didn’t take me long to find that a more Rustacean library might be a better replacement for ncurses, and I’ve (provisionally) settled on termion, since it’s a pure Rust solution, without a bunch of dependencies.
Of course, since I’m not “following the instructor’s lead”, I’m running into all sorts of barriers, but I’m having fun, and figure for a tinkerer, that’s the goal.
I figure that being part of a community of Rustaceans would be of value to me, so I signed up to this forum.
So let me start with two questions, if y’all don’t mind, one probably easy, one maybe not so.
- In Cargo.toml, one termion-tutorial site told me to add
termion = "1"
whereas another told me to add
termion = "*"
What’s the difference, and does the “1” refer to a version of termion, and if I’m looking at old documentation that might say to use something like “2.3.1”, does that limit my code to using an older version of my dependency that may have been since updated to “2.3.7” or so?
- In using termion, I want to restore my original terminal’s contents after erasing it for my choo-choo’s run across the screen, so that the screen’s contents are not lost to the computer user simply because he mistyped “ls” as “sl”.
I’ve discovered that I can create an “AlternateScreen” (a secondary screen buffer?), draw my train on this screen, and then at the end of my program that alternate screen disappears and the original screen is restored. The guts of the code I use to create this screen is:
extern crate termion;
use termion::{clear, color, cursor, style, screen::*};
use termion::raw::IntoRawMode;
use std::io::{Write, stdout};
use std::{env, time, thread};
fn main() -> Result<(), std::io::Error> {
// Enter raw mode, using a different screen than the stdout default (so we can restore the screen later).
let mut screen = AlternateScreen::from(stdout().into_raw_mode().expect("Error entering raw mode."));
// Hide the cursor (so you don't have a white rectangle bouncing around the screen).
writeln!(screen, "{}", termion::cursor::Hide).unwrap();
// Clear the screen.
writeln!(screen, "{}", clear::All).unwrap();
// Test print a message, wait 3 seconds, and then end the program, restoring the screen.
let x: u16 = 12;
let y: u16 = 22;
let obj = "This is after clearing the screen. And now we're waiting three seconds.";
writeln!(screen, "{}{}", termion::cursor::Goto(x, y), &obj).unwrap();
thread::sleep(time::Duration::from_millis(3000)); // Or use 'from_secs(3)'.
Ok(())
} // end of main()
However, if I want to move my print statement into a function, I run into a problem because “screen” is not in the function’s scope, and I can’t figure out how to pass it to the function, because of type issues.
fn print_object(&mut screen::AlternateScreen, &obj: &str, x: u16, y: u16) {
// Jump to the specified coords
writeln!(screen, "{}", termion::cursor::Goto(x, y));
// Print the object
write!(screen, "{}", &obj).unwrap();
screen.flush().unwrap(); // Force the previous write! to display (which otherwise doesn't without a /n).
} // end of print_object()
...
// Test print a message, wait 3 seconds, and then end the program, restoring the screen.
let x: u16 = 12;
let y: u16 = 22;
let obj = "This is after clearing the screen. And now we're waiting three seconds.";
// writeln!(screen, "{}{}", termion::cursor::Goto(x, y), &obj).unwrap();
print_object(&screen, &obj, x, y);
thread::sleep(time::Duration::from_millis(3000)); // Or use 'from_secs(3)'.
Ok(())
...
results in:
westk@westkent:~/PROGRAMMING/RUST/LEARNING/sl_rust$ cargo run
Compiling sl_rust v0.1.0 (/home/westk/PROGRAMMING/RUST/LEARNING/sl_rust)
error: expected one of `!`, `(`, `...`, `..=`, `..`, `::`, `:`, `{`, or `|`, found `,`
--> src/main.rs:25:45
|
25 | fn print_object(&mut screen::AlternateScreen, &obj: &str, x: u16, y: u16) {
| ^ expected one of 9 possible tokens
error[E0425]: cannot find value `screen` in this scope
--> src/main.rs:27:14
|
27 | writeln!(screen, "{}", termion::cursor::Goto(x, y));
| ^^^^^^ not found in this scope
error[E0425]: cannot find value `screen` in this scope
--> src/main.rs:29:12
|
29 | write!(screen, "{}", &obj).unwrap();
| ^^^^^^ not found in this scope
error[E0425]: cannot find value `screen` in this scope
--> src/main.rs:30:5
|
30 | screen.flush().unwrap(); // Force the previous write! to display (which otherwise doesn't without a /n).
| ^^^^^^ not found in this scope
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:25:48
|
25 | fn print_object(&mut screen::AlternateScreen, &obj: &str, x: u16, y: u16) {
| ^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `str`
= note: all local variables must have a statically known size
= help: unsized locals are gated as an unstable feature
error: aborting due to 5 previous errors
Any help would be appreciated. Thanks!