THE WRITER MUST EAT -> patreon.com/trn1ty <- | \ | | blah! |\ | `\|\ | the rantings and ravings |/ |(_|| | * of a depraved lunatic <^> 2023-10-31 : trinity writes a rust hello world Where I now find myself living (though to say I live here would be a lie) I am surrounded by a couple of the smartest people I know, and through some days of wearing me down I am donning the programmer socks and writing a Rust Hello World program. I am now actually wearing thigh highs. # apk add rust I don't actually know how to get the Rust build system going but this seems like the best option so I'll go with this which is already packaged for Chimera. Oh, I'll need cargo(1) too. # apk add cargo One of my friends built the Rust book PDF for me which is nice because I can consult it on my tablet while programming on the laptop. >Foreword >It wasn't always so clear, but the Rust programming language is fundamentally >about *empowerment*... Okay, I get why so many chan-types are so against Rust. But seeing how people who know Rust use Rust I am sort of starting to get it. It's a high level language that can be used well for systems programming, basically? >To check whether you have Rust installed correctly, open a shell and enter >this line: $ rustc --version Okay. rustc 1.73.0 (cc66ad468 2023-10-03) (Chimera Linux) Awesome! I don't have rustup so I can't read the Rust docs but I'll probably be around a web browser when programming so I think it's fine? Rust wants me to make a Hello, World! to start, but that's not super practical code for me. I think I'm gonna start smaller and make a true(1) implementation. ```rs fn main() { } ``` Works. ```rs ``` Does not work; there's no `main` function so the program doesn't know how to execute: error[E0601]: `main` function not found in crate `r#true` | = note: consider adding a `main` function to `true.rs` error: aborting due to previous error For more information about this error, try `rustc --explain E0601`. I really like the `rustc --explain` thing, this reminds me of Shellcheck. Compare to the clang error message when compiling the same file: ld: error: undefined symbol: main >>> referenced by crt1.c:18 (../crt/crt1.c:18) >>> /lib/Scrt1.o:(_start_c) >>> referenced by crt1.c:18 (../crt/crt1.c:18) >>> /lib/Scrt1.o:(_start_c) clang-16: error: linker command failed with exit code 1 (use -v to see invocati on) There's a lot going on here that the beginner (or even proficient C programmer) doesn't know and doesn't know how to start to know. Alright, what about this: ```rs fn main(); ``` error: free function without a body --> true.rs:1:1 | 1 | fn main(); | ^^^^^^^^^- | | | help: provide a definition for the function: `{ <body> }` error: aborting due to previous error Okay, so `fn main() { }` seems to be the simplest way to do this. How do I return an exit code explicitly, though, so I can make a false(1) implementation? It was at this point one of the people I know who knows Rust came by and I told them how I was coming along and they were really supportive of my very meager progress. I found some stuff here: https://doc.rust-lang.org/std/process/struct.ExitCode.html So instead of understanding everything that's happening I'll try just plugging some code in, StackOverflow style: ```rs use std::process::ExitCode; fn main() -> ExitCode { ExitCode::from(0) } ``` TRIN: Can I name you in my blog? Or should I keep saying "it was at this point one of the people with which I'm staying walked through on its pacing route"? MARS: You can say Mars, that's fine. TRIN: So you can put a constant on the last line of a function without a trailing semicolon to return that value? MARS [paraphrased]: Yeah. It's less to say, "return that value" than it is to say "this function has this value". Rust is a functional language disguised as a procedural language. Okay, that fucks. ExitCode has a SUCCESS constant I could also use, meaning the equivalent to C's `E_OK` or whatever the constant provided by stdio.h is, but I'm wary about using a library-defined constant less it changes because POSIX does not change (much). So I think this is a good Rust true(1) implementation. It can be found in src/true/true.rs. And src/false/false.rs: ```rs use std::process::ExitCode; fn main() -> ExitCode { ExitCode::from(1) } ``` I just had supper which was delicious, vegan hot dogs and some macaroni my hosts had left over. They are really delightful. Now I wanna make echo(1). This will serve as my HelloWorld as it uses stdout printing and, beyond the usual HelloWorld, very light argument handling. The book mentions cargo(1) which I will be using but for now I'll stick to single .rs files because echo(1) shouldn't have any dependencies. It looks like std::env will give me stuff relating to arguments, std::env::args or std::env::args_os. According to StackOverflow the difference is in typing. I've heard docs.rs has some documentation but looking at the site it looks like it only documents third party cargo crates, which are like C libraries but (I think) included per-project so as to not muck up the system (I hope). I looked up "rust std env" and found docs.rust-lang.org which has /std/env which was what I needed. The Rust documentation summarizes more thoroughly but, basically, an OsString, the type instances of which are iterated through by (oh my god this sentence is a prepositional mess I give up) an OsString is a fat pointer or whatever the Rust equivalent is while a String is probably just a nul-terminated sequence of bytes. Implementation-defined of course but Rust documentation notes that OsString should be converted to a CStr before being used in UNIX system calls. A nice detail I'm happy to know! I shouldn't have to do any string conversion; echo(1) should spit out *exactly* what it's given (opinion; implementations differ) just with space delimiting and newline ending. Hopefully there's a way for me to print out an OsString without conversion or anything. I need to `use std::ffi::{OsStr, OsString};` or something like that I think but I'm gonna try with just `use std::env;` at first. The use of echo(1) is defined for argc<2 (print a newline alone; argc can be zero without consequence here) and argc>=2, so it won't be necessary to return a value from main(), Rust can just use the default successful value. It looks like OsStr and OsString are from std::ffi which provides tools for FFI bindings. This also notes that the Rust String is also fat and not nul -terminated. It looks like the difference is that OsString represents an "owned platform string" and an OsStr represents a "borrowed reference to a platform string". This, I think, relates to memory management and a Borrow Checker (spooky) about which I haven't gotten around to learning. Rust's std::ffi is fascinating but while learning Rust I wanna be doing things oxidatiously or whatever and not doing a thin Rust wrapper and then my usual C bullshit. One of the things about Rust that excites me is that it seems to be able to make guarantees about project stability C can't but I don't know much about that except the stuff Mars has shown me that I don't quite understand. So how do I iterate through env::args_os? According to its reference page, ```rs use std::env; fn main() { for argument in env::args_os() { println!("{argument:?}"); } } ``` Wow! What the fuck is a println!? According to the Rust book all we need to know is that the `!` suffix is some Hungarian notation esque marker that println!() is a macro. The Rust documentation provides a definition, I think, of println: ```rs macro_rules! println { () => { ... }; ($($arg:tt)*) => { ... }; } ``` I think the `{ ... }` notes abridged portions and the [...]` => { ... };` indicates that one case is triggered by println receiving no arguments and the other case is triggered by println receiving any other amount of arguments. I don't know if this is actual code or anything but yeah uh... Rust macros. Cool. What I was actually interested in is how to print without a newline. I think there's a macro for that too. ```rs macro_rules! print { ($($arg:tt)*) => { ... }; } ``` Interesting. The documentation notes: >Prints to the standard output. > >Equivalent to the `println!` macro except that a newline is not printed at the >end of the message. >Note that stdout is frequently line-buffered by default so it may be necessary >to use `io::stdout().flush()` to ensure the output is emitted immediately. I like the note that `fflush(stdout);` is needed because this bites C beginners a lot when writing stuff that does something like `printf("> "); fgets([...]);`. I see stuff in here about `.unwrap()` and `stdout().lock()` but I hope I don't need that because I don't understand it yet. I'm just gonna use print!. So how do I print! an OsString? And how do I handle argc<2? The book chapter 12 actually touches on a lot of this and I stumbled upon it looking at std::env stuff. Here's a test I can run from the book: ```rs use std::env; fn main() { let args: Vec<String> = env::args().collect(); dbg!(args); } ``` I'll modify that a little: ```rs use std::env; fn main() { let args: Vec<OsString> = env::args().collect(); dbg!(args); } ``` $ rustc echo.rs error[E0412]: cannot find type `OsString` in this scope --> echo.rs:4:19 | 4 | let args: Vec<OsString> = env::args().collect(); | ^^^^^^^^ --> /builddir/rust-1.73.0/library/alloc/src/string.rs:365:1 | = note: similarly named struct `String` defined here | help: a struct with a similar name exists | 4 | let args: Vec<String> = env::args().collect(); | ~~~~~~ help: consider importing this struct | 1 + use std::ffi::OsString; | error: aborting due to previous error For more information about this error, try `rustc --explain E0412`. Okay. $ sed -e '1a use std::ffi::OsString' <echo.rs >echo.2.rs $ rustc echo.2.rs error[E0277]: a value of type `Vec<OsString>` cannot be built from an iterator over elements of type `String` --> echo.rs:5:43 | 5 | let args: Vec<OsString> = env::args().collect(); | ^^^^^^^ value of type `Vec<OsStri ng>` cannot be built from `std::iter::Iterator<Item=String>` | = help: the trait `FromIterator<String>` is not implemented for `Vec<OsString>` = help: the trait `FromIterator<T>` is implemented for `Vec<T>` note: required by a bound in `collect` --> /builddir/rust-1.73.0/library/core/src/iter/traits/iterator.rs:2049:5 error: aborting due to previous error For more information about this error, try `rustc --explain E0277` Oh shit, I forgot to change env::args to env::os_args. $ sed -e '5s.args.os_args.' <echo.2.rs >echo.rs $ rustc echo.rs error[E0425]: cannot find function `os_args` in module `env` --> echo.rs:5:36 | 5 | let args: Vec<OsString> = env::os_args().collect(); | ^^^^^^^ help: a function with a similar name exists: `args_os` --> /builddir/rust-1.73.0/library/std/src/env.rs:793:1 | = note: similarly named function `args_os` defined here error: aborting due to previous error For more information about this error, try `rustc --explain E0425`. Oops. $ sed -e '5s.os_args.args_os.' <echo.rs >echo.2.rs $ rustc echo.2.rs $ So presumably it compiled. $ ./echo [echo.rs:6] args = [ "./echo", ] Okay, that debug macro is kinda awesome. The 500K binary makes me kinda weirded out, what's the size of the actual echo.c (which is the complete program) when compiled for arm64 (my current architecture)? .rwxr-xr-x trinity trinity 9.8 KB Tue Oct 31 21:01:27 2023 🏗 a.out This output is prettier than usual because I'm using lsd(1), a reimplementation of the standard POSIX ls(1). My girlfriend in Florida uses it and it's really pleasant and color codes some stuff in a way that's very useful. 10K is a lot less than half a meg. I wonder if Rust is statically compiling versus relying on system library stuff. I don't wanna bother looking this up so I'll go ask Mars. Its door is closed so I'll look this up. "why are rust binaries so big" popped up a StackOverflow post that started with "Rust uses static linking" so that answers my question. I would assume a statically linked C executable would be about that big, from memory I think this is true but don't wanna bother testing because I don't have the energy to look up clang arguments. $ cc -static echo.c ld: error: unable to find library -l:libunwind.a ld: error: unable to find library -latomic ld: error: unable to find library -lc clang-16: error: linker command failed with exit code 1 (use -v to see invocati on) Yeah, I'm not sorting that out, I'm not building C stuff on here to distribute. I think vec.len() will tell me how many arguments I've received? ```rs use std::env; use std::ffi::OsString; fn main() { let args: Vec<OsString> = env::args_os().collect(); dbg!(args); dbg!(args.len()); } ``` $ rm echo.2.rs $ rustc echo.rs error[E0382]: borrow of moved value: `args` --> echo.rs:7:10 | 5 | let args: Vec<OsString> = env::args_os().collect(); | ---- move occurs because `args` has type `Vec<OsString>`, which doe s not implement the `Copy` trait 6 | dbg!(args); | ---------- value moved here 7 | dbg!(args.len()); | ^^^^ value borrowed here after move | error: aborting due to previous error For more information about this error, try `rustc --explain E0382`. Okay, so now I'm talking to the borrow checker. Maybe if I assign the length to a variable it'll work? I don't know what I'm doing. ```rs use std::env::args_os; use std::ffi::OsString; fn main() { let args: Vec<OsString> = args_os().collect(); let argc = args.len(); dbg!(args); dbg!(argc); } ``` $ rustc echo.rs $ ./echo [echo.rs:7] args = [ "./echo", ] [echo.rs:8] argc = 1 Okay. I don't know why that works but it does. Something to do with memory management. That's not a big deal to me because I understand when I do fucky wucks like ```py try: print("c = " + str( (float(input("a = ")) ** 2 + float(input("b = ")) ** 2) ** 0.5)) except ValueError: print("input must be a number") except: pass ``` there's a lot of memory shit happening behind the scenes I don't have to worry about, unlike in the equivalent C where I would have to handle buffer overflows (I personally would toss the excess and skip to the newline) and string to float conversion. Rust requiring some steps Python wouldn't makes sense to me because while Rust is less pedantic it doesn't lie to me (much). Let me try something now: ```rs use std::env::args_os; use std::ffi::OsString; fn main() { let argv: Vec<OsString> = args_os.collect(); let argc = argv.len(); if argc < 2 { println!(); } else { dbg!(argv); } } ``` $ rustc echo.rs $ ./echo | hexdump -C 00000000 0a |.| 00000001 $ ./echo piss shit [echo.rs:11] argv = [ "./echo", "piss", "shit", ] Cool stuff. I don't think Rust has ternaries so I'm not gonna be able to do language tricks to make the code really compact like my C implementation: ```c #include <stdio.h> /* NULL, fprintf(3), putc(3) */ #include <stdlib.h> /* stdout */ #include <sysexits.h> /* EX_OK */ int main(int argc, char **argv){ if(*argv == NULL || *++argv == NULL){ argc = 1; putc('\n', stdout); } while(--argc) fprintf(stdout, "%s%c", *(argv++), argc > 1 ? ' ' : '\n'); return EX_OK; } ``` Something I really like is that whereas in C I note what I use from headers in comments like a total tool, Rust lets me bring individual structures and functions in so I can keep track of my dependencies in code alone. I wonder if I can ```rs use std::env::args_os; fn main() { let argc = args_os().collect().len(); dbg!(argc); } ``` $ rustc echo.rs error[E0282]: type annotations needed --> echo.rs:5:26 | 4 | let argc = args_os().collect().len(); | ^^^^^^^ cannot infer type of the type parameter `B ` declared on the method `collect` | help: consider specifying the generic argument | 4 | let argc = args_os().collect::<Vec<_>>().len(); | ++++++++++ error: aborting due to previous error; 1 warning emitted For more information about this error, try `rustc --explain E0282`. Okay, how about ```rs use std::env::args_os; use std::ffi::OsString; fn main() { let argc = args_os().collect::Vec<OsString>().len(); dbg!(argc); } ``` I guess function::type() specifies the type of which function should be returning. That sort of makes sense? C doesn't have generic functions like that but I think I understand some of what's happening there. $ rustc echo.rs error: generic parameters without surrounding angle brackets --> echo.rs:5:35 | 5 | let argc = args_os().collect::Vec<OsString>().len(); | ^^^^^^^^^^^^^ | help: surround the type parameters with angle brackets | 5 | let argc = args_os().collect::<Vec<OsString>>().len(); | + + error: aborting due to previous error Okay. I'm changing that without copying my code because I'm not motivated to do so. Also the actual errors are probably not byte-for-byte if for whatever reason you're following along at home (why would you? I don't know what I'm doing) because my code actually has a ton of snippets commented out so I don't need to retype everything. I made the changes it suggested and the program works. Neat. But do I need that local variable? ```rs use std::env::args_os; use std::ffi::OsString; fn main() { if args_os().collect::<Vec<OsString>>().len() < 2 { println!(); } else { } } ``` $ rustc echo.c $ No I don't! Only if I'm using it more than once, which makes sense. I'd like to forego println!() though because I have a feeling this prelude-provided macro will do platform-specific things and differ on NT vs UNIX due to line ending conventions. I don't like that for a program that's supposed to follow POSIX. It looks like std::io::Stdout exists so I'm gonna use that and put a lock on std::stdout so I can write to it. I think this works? ```rs use std::env::args_os; use std::io::{Write, stdout}; use std::ffi::OsString; fn main() { let mut stdout = stdout().lock(); if args_os().collect::<Vec<OsString>>().len() < 2 { stdout.write(b"\n"); // Rust wants a 'b' prefix } else { } } ``` $ rustc echo.rs warning: unused `Result` that must be used --> echo.rs:8:9 | 8 | stdout.write(b"\n"); | ^^^^^^^^^^^^^^^^^^^ | = note: this `Result` may be an `Err` variant, which should be handled = note: `#[warn(unused_must_use)]` on by default help: use `let _ = ...` to ignore the resulting value | 8 | let _ = stdout.write(b"\n"); | +++++++ warning: 1 warning emitted Okay, a note that I should handle the possibility of an error. I don't know how to do that so I won't, like a true in-the-field professional. I guess b"\n" is a Rust byte string. I don't think it's super important just yet for me to know what that is so I'm gonna assume I'm fine. I'm feeling devious. ```rs use std::env::args_os; use std::io::{Write, stdout}; use std::ffi::OsString; fn main() { let mut stdout = stdout().lock(); if args_os().collect::<Vec<OsString>>().len() >= 2 { for argument in args_os() { stdout.write(argument); stdout.write(b" "); } } stdout.write(b"\n") } ``` $ rustc echo.c error[E0308]: mismatched types --> echo.rs:9:26 | 9 | stdout.write(argument); | ----- ^^^^^^^^ expected `&[u8]`, found `OsString` | | | arguments to this method are incorrect | note: method defined here --> /builddir/rust-1.73.0/library/std/src/io/mod.rs:1461:8 error: aborting due to previous error For more information about this error, try `rustc --explain E0308`. So I could look up how to turn an OsString into a `&[u8]` but I need to know what that is because echo(1) shouldn't be dependent on "proper input" (UTF-1 should work as well as UTF-8). I checked the std::ffi::OsString methods but none of them really told me anything I think I can use so I'm gonna look at std::io. Looking at the primitive u8, it's an 8-bit unsigned integer which should be fine for my uses. The method into_os_str_bytes() should work to convert std::ffi::OsString into a Vec<u8> but the documentation notes that this is "a nightly-only experimental API". Whatever, probably fine. ```rs use std::env::args_os; use std::io::{Write, stdout}; use std::ffi::OsString; fn main() { let mut stdout = stdout().lock(); if args_os().collect::<Vec<OsString>>().len() >= 2 { for argument in args_os() { stdout.write(argument.into_os_str_bytes()); stdout.write(b" "); } } stdout.write(b"\n"); } ``` $ rustc echo.c error[E0658]: use of unstable library feature 'os_str_bytes' --> echo.rs:9:35 | 9 | stdout.write(argument.into_os_str_bytes()); | ^^^^^^^^^^^^^^^^^ | = note: see issue #111544 <https://github.com/rust-lang/rust/issues/111544> f or more information error[E0308]: mismatched types --> echo.rs:9:26 | 9 | stdout.write(argument.into_os_str_bytes()); | ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&[u8]`, fou nd `Vec<u8>` | | | arguments to this method are incorrect | = note: expected reference `&[u8]` found struct `Vec<u8>` note: method defined here --> /builddir/rust-1.73.0/library/std/src/io/mod.rs:1461:8 help: consider borrowing here | 9 | stdout.write(&argument.into_os_str_bytes()); | + error: aborting due to 2 previous errors Some errors have detailed explanations: E0308, E0658. For more information about an error, try `rustc --explain E0308`. Okay, I'll add that ampersand the borrow checker desires. I'm not sure how this works still. $ rustc echo.rs error[E0658]: use of unstable library feature 'os_str_bytes' --> echo.rs:9:36 | 9 | stdout.write(&argument.into_os_str_bytes()); | ^^^^^^^^^^^^^^^^^ | = note: see issue #111544 <https://github.com/rust-lang/rust/issues/111544> f or more information error: aborting due to previous error For more information about this error, try `rustc --explain E0658`. So how do I use an unstable library feature? I'll use the rustc facilities. $ rustc --explain E0658 This brought me into a manual snippet shown in my configured pager (I think) with instructions on how to add a feature flag. I then did what it said and wasn't anywhere better so I wonder if there's another way to turn an OsString into a &[u8]. Then Mars came into the room and greeted me and I asked it how to make this shit work. Apparently an issue is I'm running stable rustc and in order to use nightly rustc stuff I need nightly rustc provided by using rustup instead of the packaged rust toolchain. I don't really wanna do that but I also don't really wanna give up so I think I'm just gonna make this a shitty echo(1) implementation that limits input to UTF-8. But first I wanna see how someone else has done this already. https://github.com/uutils/coreutils.git src/uu/echo/src/echo.rs L119: >pub fn uumain(args: impl uucore::Args) -> UResult<()> { > let args = args.collect_lossy(); > let matches = uu_app().get_matches_from(args); > > let no_newline = matches.get_flag(options::NO_NEWLINE); > let escaped = matches.get_flag(options::ENABLE_BACKSLASH_ESCAPE); > let values: Vec<String> = match matches.get_many::<String>(options::STRING ) { > Some(s) => s.map(|s| s.to_string()).collect(), > None => vec![String::new()], > }; > > execute(no_newline, escaped, &values) > .map_err_context(|| "could not write to stdout".to_string()) >} Those rat bastards did std::env::args.collect_lossy()! Those utter tools! I imagine this doesn't work for binary data but I don't know and I'm not building this because I don't wanna figure out how to right now. Everyone is going to sleep now except me so I now feel like I need to get an echo(1) implementation working on this, the first day I've actually started to learn Rust. I'm just gonna go with std::env::args and Strings. Mars also mentioned some Rust types stuff, namely &[u8] being a borrowed slice of u8s or something. I sort of got it and sort of didn't, I did at the time I just forgot. Sorry! Also it came back out after I wrote that to greet me and then promptly disappeared. This spits out a lot of warnings: ```rs use std::env::args; use std::io::{Write, stdout}; fn main() { let mut stdout = stdout().lock(); if args().collect::<Vec<String>>().len() >= 2 { for argument in args() { stdout.write(&argument.as_bytes()); stdout.write(b" "); } } stdout.write(b"\n"); } ``` This is nice but print!() handles errors I think so I'm just going back to that. ```rs use std::env::args; fn main() { if args().collect::<Vec<String>>().len() >= 2 { for argument in args() { print!(argument); print!(" "); } } print!("\n"); } ``` $ rustc echo.c error: format argument must be a string literal --> echo.rs:6:20 | 6 | print!(argument); | ^^^^^^^^ | help: you might be missing a string literal to format with | 6 | print!("{}", argument); | +++++ error: aborting due to previous error Okay. ```rs use std::env::args; fn main() { if args().collect::<Vec<String>>().len() >= 2 { for argument in args() { print!("{}", argument); print!(" "); } } print!("\n"); } ``` $ rustc echo.c $ ./echo hello world ./echo hello world The issue is the first argument is coming along for the ride in that for loop. How do I skip the first iteration of an iterator? [trial and error with .rs files and rustc omitted] Oh. ```rs use std::env::args; fn main() { if args().collect::<Vec<String>>().len() >= 2 { for argument in args().skip(1) { print!("{} ", argument); } } print!("\n"); } ``` $ rustc echo.c $ ./echo Hello, world! Hello, world! $ ./echo Happy Halloween! Happy Halloween! That's where I'm leaving my Rust education today. And this is day 1. Pretty good! <^> No rights reserved, all rights exercised, rights turned to lefts, left in this corner of the web.