How To Pass AlternateScreen to a Function?

Caveat: I don't know what I'm doing; I'm just tinkering, and have no real programming experience

To scratch my own itch, I'm trying to rewrite the Debian joke-app "sl" (draws an animated ASCII train going across the screen when the user mistypes "ls" as "sl") in Rust (just to see if I can do it).

I've gotten far enough to know how to create an alternate screen, print some text at a specified location, and then quit the program, restoring the original (non-alternate) screen. Because I'll be printing multiple frames of the train, I figure the cusor-positioning and printing of text should be in a function, but I can't figure out how to pass the AlternateScreen item to the function. Here's my basic code, followed by an example error I get at compile time (I've tried other permutations, and gotten other errors, but the point is that I've figured out multiple ways of not doing it, but I can't figure out how to do it):

extern crate termion;

use termion::{clear, screen::*};
use termion::raw::IntoRawMode;
use std::io::{Write, stdout};
use std::{time, thread};

fn print_object(&mut screen: termion::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()

fn main() -> Result<(), std::io::Error> {
    // Define useful variables
    let obj: &str;

    // 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).expect("Error hiding the cursor.");
    
    // Clear the screen.
    writeln!(screen, "{}", clear::All).expect("Error clearing the screen.");
    
    // Test print a message, wait 3 seconds, and then end the program, restoring the screen.
    let x: u16 = 12;
    let y: u16 = 22;
    obj = "This is after clearing the screen. And now we're waiting three seconds.";
    print_object(&screen, &obj, x, y);
//    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()

And the compiler error:

westk@westkent:~/PROGRAMMING/RUST/LEARNING/sl_rust$ cargo run
   Compiling sl_rust v0.1.0 (/home/westk/PROGRAMMING/RUST/LEARNING/sl_rust)
error[E0107]: wrong number of type arguments: expected 1, found 0
  --> src/main.rs:25:30
   |
25 | fn print_object(&mut screen: termion::screen::AlternateScreen, &obj: &str, x: u16, y: u16) {
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 1 type argument

error: aborting due to previous error

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

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

Any help in figuring this out would be appreciated. Thanks!

Your code snippet:

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

And your error snippet:

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

Don't match (the <W> is the type argument), so it's hard to give any further advice.

Sorry; that code snippet was from a previous attempt; I've corrected the snippet now to reflect the actual code I compiled to get the error snippet.

Note: In both the snippets below, I've also removed or rearranged your use of & and &mut on the left side of your parameters, e.g. &mut screen: ... and &obj: &str. Explaining that is probably worth a follow-up comment of its own.


I don't have termion at hand right now, so I haven't actually checked these, but you probably want something like:

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

Which is a generic function that will work with any AlternateScreen<W> where W implements the Write trait.

You could probably see what actual type is being used by seeing what the error for this signature is instead:

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

(You'll get a type mis-match between the () in AlternateScreen<()> and whatever it is you're actually passing in -- probably RawTerminal<StdOut> or such.)

But if the bounds on the generic version are sufficient, that's the approach I suggest.

I appreciate your response.

I already have:

use std::io::{Write, stdout};

My understanding is pretty muddy, but I understand this to be equivalent to:

use std::io::Write;
use std::io::stdout;

So I think I don't need to add your use line; I think you're just pointing out that this use needed in some way. Correct? (So I don't plan to add it separately.)

Earlier I had tried something similar to the fn line you provided, but the rearranegment and the addition of ": Write" to "" seems to have helped (I had earlier just had "..ject(..." instead of "..ject<W: Write>(...".)

Now I'm getting a different error ("&obj doesn't have a size known at compile time"), but I'll bang on it a while before asking for more help (I thought that was one of the points of the "&", to turn it into a "pointer" with a known size; huh, guess not).

For now, I think you've gotten me past the big hurdle. I'll report back here once I get this proof-of-concept working. Thank you very much!

Oh, and just to satisfy anyone's curiosity about the compiler's reported type for AlternateScreen, using @quinedot's suggestion:

westk@westkent:~/PROGRAMMING/RUST/LEARNING/sl_rust$ cargo run
   Compiling sl_rust v0.1.0 (/home/westk/PROGRAMMING/RUST/LEARNING/sl_rust)
error[E0277]: the trait bound `(): std::io::Write` is not satisfied
  --> src/main.rs:10:25
   |
10 | fn print_object(screen: &mut termion::screen::AlternateScreen<()>, &obj: &str, x: u16, y: u16) {
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::io::Write` is not implemented for `()`
   | 
  ::: /home/westk/.cargo/registry/src/github.com-1ecc6299db9ec823/termion-1.5.6/src/screen.rs:49:31
   |
49 | pub struct AlternateScreen<W: Write> {
   |                               ----- required by this bound in `termion::screen::AlternateScreen`

error: aborting due to previous error

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

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

Nope, not there yet. I made this change:

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

(Removing the "&" from "obj" here (but leaving it on the calling line) got rid of my last-reported error.)

Now I'm getting this:

   Compiling sl_rust v0.1.0 (/home/westk/PROGRAMMING/RUST/LEARNING/sl_rust)
error[E0308]: mismatched types
  --> src/main.rs:37:18
   |
37 |     print_object(&screen, &obj, x, y);
   |                  ^^^^^^^ types differ in mutability
   |
   = note: expected mutable reference `&mut termion::screen::AlternateScreen<_>`
                      found reference `&termion::screen::AlternateScreen<RawTerminal<Stdout>>`

if I take the "&" out of my calling statement, I get this:

   Compiling sl_rust v0.1.0 (/home/westk/PROGRAMMING/RUST/LEARNING/sl_rust)
error[E0308]: mismatched types
  --> src/main.rs:37:18
   |
37 |     print_object(screen, &obj, x, y);
   |                  ^^^^^^
   |                  |
   |                  expected mutable reference, found struct `termion::screen::AlternateScreen`
   |                  help: consider mutably borrowing here: `&mut screen`
   |
   = note: expected mutable reference `&mut termion::screen::AlternateScreen<_>`
                         found struct `termion::screen::AlternateScreen<RawTerminal<Stdout>>`

Ooh! Wait! I got it. I just had to pay a little more attention to the error message. Here's the fix:

print_object(&mut screen, &obj, x, y);

Thank you very much for your help!

1 Like

Glad you got there!

So, this has to do with patterns. You may already be somewhat familiar with patterns by using match, but patterns are also used for let and for function parameters, too. So when we write this:

fn print_coordinates(&(x, y): &(i32, i32)) {

Then within the function, x and y are i32s. And when you wrote something like this:

fn foo(&obj: &str) {

You were saying that obj should be a str. But really you wanted obj to be a &str.

This can be a stumbling block when coming from languages that put the type on the left. If in doubt, just have the name of the parameter on the left. You may need mut name if you mutate the parameter, just like you would have let mut name.