0

I am playing with the following two code snippets

// Snippet1 in C
#include <stdio.h>

int main(){
    FILE * fp = fopen("/dev/tty", "r");

    int c = getc(fp);
    printf("%d", c);
}
// Snippet2 in Rust
use std::io::Read;
use std::fs;

fn main() {
    let mut f: fs::File = fs::File::open("/dev/tty").unwrap();
    let mut buf: [u8;1] = [0];

    f.read_exact(&mut buf).unwrap();
    print!("byte: {}", buf[0]);
}

What the above code wants to do is to read a byte from the user keyboard and then print it to stdout. The confusing thing is two snippets have different behaviors:

➜  playground gcc main.c -o main
➜  playground ./main
a                                  # input a and press Enter
97%
➜  playground cargo run -q
a                                  # input a and press Enter
byte: 97%                                                                ➜  playground
➜  playground

I am sorry about the format of the above code, I don't know how to place the prompt at the start of a new line:(

Please note, there are two shell prompts ➜ playground after the execution of Rust code?

I am guessing the Enter is sent to the input buffer as if I have pressed it after execution.

If I know what is actually happening in the buffer, I will find out the cause of this distinction, so I am wondering what's going on there?

BTW, my environment:

➜  playground rustc --version
rustc 1.57.0 (f1edd0429 2021-11-29)
➜  playground gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

If my question is not allowed, feel free to ask me to delete it:) Thanks in advance:)

Steve Lau
  • 658
  • 7
  • 13
  • Well, `read_exact` is clearly not reading the newline character, since it wouldn't fit in the buffer. I guess that `getc` just silently swallows it? – Cerberus Jan 18 '22 at 10:24
  • Newline is written to /dev/tty actually. So if getc is called again, it should read the newline character. – kiner_shah Jan 18 '22 at 10:36
  • @Cerberus Thx for your comment! There are two kinds of reading streams, one reads directly from the keyboard(char a), and the other reads from stdin(Enter keystroke). And in my opinion, `getc` belongs to the former cause it only reads from its parameter(file /dev/tty), perhaps it's something else that swallows the enter? – Steve Lau Jan 18 '22 at 10:42
  • @kiner_shah, Thx for the comment! Can you explain why `Newline is written to /dev/tty actually` further? What I think is when I type 'a', `getc` is done with its work, and newline is written to the input buffer. – Steve Lau Jan 18 '22 at 10:46
  • @SteveLau, I ran the following code and you can see 10 is output (ASCII for newline) as soon as you print enter. https://onlinegdb.com/AUkca3lGy – kiner_shah Jan 18 '22 at 10:48

1 Answers1

3

Note that in C, FILE* and the corresponding functions are buffered, so the C program reads the key and newline characters and puts them in its buffer, whereas your Rust code doesn't use buffering. As a consequence the newline character is still in the kernel buffer for the TTY when your Rust program finishes and so the newline is seen by your shell, whereas the newline has been removed from the kernel buffer by your C program.

You should get the same behaviour as your Rust program with this unbuffered C code:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int fd = open ("/dev/tty", O_RDONLY);

    char c;
    read (fd, &c, 1);
    printf("%d", c);
}

or the same behaviour as your C program with this buffered Rust code:

use std::io::{ BufReader, Read };
use std::fs;

fn main() {
    let mut f = BufReader::new (fs::File::open("/dev/tty").unwrap());
    let mut buf: [u8;1] = [0];

    f.read_exact(&mut buf).unwrap();
    print!("byte: {}", buf[0]);
}
Jmb
  • 18,893
  • 2
  • 28
  • 55
  • So basically, in Rust, when enter is pressed another echo happens leading to the behavior of enter being pressed twice right? But in C, the echo doesn't happen, because the enter is in the buffer and is unprocessed. – kiner_shah Jan 18 '22 at 11:24
  • 1
    @kiner_shah no there is only ever a single echo, but in unbuffered mode (like the OP Rust) the "enter" is not captured by the program and so remains available for the shell, whereas in buffered mode (like the OP C) the "enter" is captured and discarded by the program and so is no longer visible for the shell. – Jmb Jan 18 '22 at 13:42
  • I think my understanding of this buffering concept is incorrect, need to revise it :-) Thanks guys. – kiner_shah Jan 19 '22 at 05:37
  • A supplement after me reading The Linux Programming Interface Ch13: 1. there are actually two buffers, the kernel buffer cache, and the libc buffer. My C snippet uses `fopen` and is buffered, this is the libc buffer. 2. In my Rust code, the newline character still resides the kernel buffer cache after this Rust process terminates, why does the shell can read it? This is because both my Rust process and shell are accessing the same disk file, which is `/dev/tty` – Steve Lau Sep 13 '22 at 04:04