Learning Rust :safe:

The guy who made this Introduction - Easy Rust also made a videos series to go along with it. I found he posts regularly still and is up to episode “Easy Rust 120: Multiple threads (concurrency) - part 2” This is the first one he posted - Easy Rust 001: Introduction and how to use the Rust Playground - YouTube

I think a great way to get people to try Rust without even installing anything because http://play.rust-lang.org/ is used through the series.

4 Likes

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.

  1. 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?

  1. 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!

2 Likes

Shouldn’t it be something more like &mut screen: AlternateScreen? I’m also new to Rust, so take this suggestion with lots of distance :slight_smile:

1 Like

I don’t like Rust for it’s dependency hell, reminds me of JavaScript’s node_modules. I compiled dynamic library with sn_api as a dependency and it’s 147 MB large, and during compilation it downloads and unpacks 2 GB data. Isn’t it crazy?

Yes, @Ioziniak, I believe you’re correct.

When I make that change, I get this (I’m not worried about the warnings; I’ll deal with those later; I also know there’s a compiler option I could temporarily add to the top of my program to turn those off, but I don’t remember the syntax, and looking it up is low-priority for me):

kent@westk-9463:~/projects/rustacean/sl$ cargo run
   Compiling sl v0.1.0 (/home/kent/projects/rustacean/sl)
warning: unused imports: `color`, `cursor`, `style`
 --> src/main.rs:3:22
  |
3 | use termion::{clear, color, cursor, style, screen::*};
  |                      ^^^^^  ^^^^^^  ^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused import: `env`
 --> src/main.rs:6:11
  |
6 | use std::{env, time, thread};
  |           ^^^

error[E0107]: missing generics for struct `termion::screen::AlternateScreen`
  --> src/main.rs:8:30
   |
8  | fn print_object(&mut screen: AlternateScreen, &obj: &str, x: u16, y: u16) {
   |                              ^^^^^^^^^^^^^^^ expected 1 type argument
   |
note: struct defined here, with 1 type parameter: `W`
  --> /home/kent/.cargo/registry/src/github.com-1ecc6299db9ec823/termion-1.5.6/src/screen.rs:49:12
   |
49 | pub struct AlternateScreen<W: Write> {
   |            ^^^^^^^^^^^^^^^ -
help: use angle brackets to add missing type argument
   |
8  | fn print_object(&mut screen: AlternateScreen<W>, &obj: &str, x: u16, y: u16) {
   |                                             ^^^

error: aborting due to previous error; 2 warnings emitted

For more information about this error, try `rustc --explain E0107`.
error: could not compile `sl`

To learn more, run the command again with --verbose.

Just a caution, if you are compiling Debug mode you will end up with large objects. Rust also favours static libs (I definitely do as I think shared libs are terrible and a security nightmare). 147Mb seems huge though and it should not download anything except other crates src code for compilation.

1 Like

The problem here is you have a type but no local variable name for it. ie you need to change &mut screen::AlternateScreen, to screen : &mut screen::AlternateScreen, if you see what I mean? So inside function refer to screen

I also know there’s a compiler option I could temporarily add to the top of my program to turn those [warnings] off, but I don’t remember the syntax, and looking it up is low-priority for me):

It’s:

#![allow(unused_imports)]

1 Like

My original post had a pasting typo, so it said:

fn print_object(&mut screen::AlternateScreen, &obj: &str, x: u16, y: u16) {

It should have said (and has been corrected to, after @Ioziniak pointed it out):

fn print_object(&mut screen: AlternateScreen, &obj: &str, x: u16, y: u16) {

This results in:

kent@westk-9463:~/projects/rustacean/sl$ cargo run
   Compiling sl v0.1.0 (/home/kent/projects/rustacean/sl)
error[E0107]: missing generics for struct `termion::screen::AlternateScreen`
  --> src/main.rs:11:30
   |
11 | fn print_object(&mut screen: AlternateScreen, obj: &str, x: u16, y: u16) {
   |                              ^^^^^^^^^^^^^^^ expected 1 type argument
   |
note: struct defined here, with 1 type parameter: `W`
  --> /home/kent/.cargo/registry/src/github.com-1ecc6299db9ec823/termion-1.5.6/src/screen.rs:49:12
   |
49 | pub struct AlternateScreen<W: Write> {
   |            ^^^^^^^^^^^^^^^ -
help: use angle brackets to add missing type argument
   |
11 | fn print_object(&mut screen: AlternateScreen<W>, obj: &str, x: u16, y: u16) {
   |                                             ^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0107`.
error: could not compile `sl`

To learn more, run the command again with --verbose.

If I add the “<W>”:

fn print_object(&mut screen: AlternateScreen<W>, obj: &str, x: u16, y: u16) {

I get:

kent@westk-9463:~/projects/rustacean/sl$ cargo run
   Compiling sl v0.1.0 (/home/kent/projects/rustacean/sl)
error[E0412]: cannot find type `W` in this scope
  --> src/main.rs:11:46
   |
11 | fn print_object(&mut screen: AlternateScreen<W>, obj: &str, x: u16, y: u16) {
   |                -                             ^ not found in this scope
   |                |
   |                help: you might be missing a type parameter: `<W>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0412`.
error: could not compile `sl`

To learn more, run the command again with --verbose.

If I use:

fn print_object(&mut screen: AlternateScreen<W: Write>, obj: &str, x: u16, y: u16) {

I get errors.

If I use:

fn print_object(&mut screen: <AlternateScreen<W: Write>>, obj: &str, x: u16, y: u16) {

I get errors.

Yes you are putting in a template param (W) but you don’t have a W type in your code. I think what you are looking for is perhaps simpler. See this example in redox Dropping of AlternateScreen does not flush stdout (#158) · Issues · redox-os / termion · GitLab So instead of trying to use a separate function and passing in a screen as a type param then write to an alternative screen then flush that. You can also clear that screen if you wish and write to the original screen again.

2 Likes

Today I learned about patch as a way to globally override dependencies in Cargo.toml

https://doc.rust-lang.org/edition-guide/rust-2018/cargo-and-crates-io/replacing-dependencies-with-patch.html

Instead of repeatedly doing this all up and down the dependency chain

[dependencies]
-threshold_crypto = "0.4.0"
+threshold_crypto = { path = "../threshold_crypto" }

do this at the top level Cargo.toml

[dependencies]
threshold_crypto = "0.4.0"

+[patch.crates-io]
+threshold_crypto = { path = "../threshold_crypto" }

This is super handy for replacing a very broadly used package like threshold_crypto from the root, eg only replace it in sn_api instead of also in sn_client, sn_transfers, sn_data_types etc… Very handy!

6 Likes

I’m afraid I’m too much of a noob to understand this. And looking at the link you included, I’m not able to follow that either. What I do believe I understand is “instead of trying to use a separate function”; it seems you’re suggesting I not use a function. But in order to move my train across the screen, I’ll be calling this routine upwards of hundreds of times, perhaps once to draw the frame, once to erase the frame, and then repeat for as many columns are in the terminal window; that sounds to me like the ideal use-case for a function. I don’t understand what you’re suggesting as an alternative.

(btw, I don’t know anything about “template params” or “W”; I’m just shooting in the dark based on the error messages generated by the compiler.)

1 Like

I can say that if you are learning this kind of thing then our own @happybeing has done a fantastic job with vdash here GitHub - happybeing/vdash: Safe Network Vault Dashboard I just spent an hour going over the code and I must say, impressed is an understatement. Take a peek, you will learn loads there.

I would stick with crossterm rather than termion, but perhaps Mark can let us know why he went with both? In any case first-class code for sure. Nice and neat too.

10 Likes

Thanks David, that’s a great compliment especially on my first Rust project. More luck than understanding I suspect, although the basics of programming are common to all languages, and I do have some experience :wink:

I went with both because I didn’t know if it would matter, so hedged my bets. At this point crossterm might in fact be best if you want one style of build to work for Windows and non-Windows, otherwise termion might well be best but I can’t say. I didn’t hit any issues with either.

6 Likes

It’s really good. I was thinking of a wee toolset for testing and using vdash as a good example. One thing I noticed recently was GitHub - gin66/tui-logger: Logger and Widget for rust's tui crate which could help a few such tools.

1 Like

I starred that at some time but have forgotten about it. Yes looks very useful and might be a better UI than the raw logfile window, which is very limited.

I’m not planning more than simple enhancements to vdash for the time being though, keeping my computer activity to a minimum for the foreseeable. I’ll continue maintaining vdash while it is useful.

5 Likes

"qaul.net is an Internet independent P2P wireless mesh communication app for activists, people off the grid and people in hostile environments.

We are looking for engaged people who would like to work as freelancer in developing the rewrite of the Wifi/Bluetooth based peer to peer communication app for Android and Linux. The rewrite and all core features are written in Rust.

We are therefore looking especially for:

  • Rust developers
  • Android developers
  • People with experience in Bluetooth Low Energy, Wifi-Direct, Wifi-Aware in Linux or/and Android.
  • EmberJS developers

Please write to contact@qaul.net

Best Mathias"

Was reading about mesh networks and ran into this. Any budding rustaceans looking for some fun, maybe would be interesting.

6 Likes

Endless trial-and-errors with borrowed variable errors.
Variables that cannot be cloned.

Like global variables?

No, values inside lists/objects.
For example you need to consume an object and a variable inside that object.
You can clone the object, but not the variable.
Do I want to clone a whole object just to get it’s variable?

This is just an example of my experience by the way.
Not something concrete I’m currently struggling with.
I haven’t played around with Rust in some time.