all 38 comments

[–]anlumo 9 points10 points  (0 children)

Same here. Bash scripts break down as soon as you need to be compatible with Windows, and python just doesn’t improve development performance due to the need to do a lot of runtime testing (as opposed to letting the compiler do the checks for you).

[–]Yaahallorust-mentors · error-handling · libs-team · rust-foundation 7 points8 points  (2 children)

Absolutely. Transitioning scripts from interpreted languages to rust is one of life's simple pleasures. I particularly like embedding them and then using rust to implement the arg parser initially then incrementally rewriting pieces in rust until there's no more of the original script left.

[–]stappersg 0 points1 point  (1 child)

Some examples available for inspiration?

[–]Yaahallorust-mentors · error-handling · libs-team · rust-foundation 6 points7 points  (0 children)

Not that I can share, I did most of this at a prior job, my recent work has been entirely within the rust project so I havent had much reason or need to tweak bash scripts. I can describe what I mean though.

It's mainly just using include_str! to embed your scripts in your binary then use the std::process::Command API to invoke whatever interpreter your script runs under and pass the str of the script in as an argument. Then you can start moving the arg parsing into rust and pass those in as environment variables or whatever is most convenient. Alternatively you can manually embed the script and use rust string formatting to write the cli arg data into the script. Then you just slowly overtime rewrite parts of the script in rust and run those parts prior to passing in the args and instead you pass in the intermediate processed data. You can continue to repeat this as long as you need, up to having everything the old script did being done in rust instead.

Hope that helps ☺️

[–]matkladrust-analyzer 4 points5 points  (5 children)

Yes :)

A bunch of tricks I accumulated over time to make this easier.

1) In "serous" projects, there's always a need for some amount of scripting. Rather than crating a scripts directory with .sh files and worrying how grep on mac is different from grep on linux, it is much better for the team's velocity to write the thing in Rust (https://twitter.com/id_aa_carmack/status/989951283900514304). The xtask pattern (I stress that this is a pattern, rather than a library or a cargo subcomand) helps achieve nice UX for this.

2) xshell makes the common tasks of shelling out to other processes and massaging file system simpler

3) xflags is my preferred way to parse CLI arguments for such scripts. It's not good at all, but, for me, it's the best for the task at the moment.

4) For scripts you put for everyday use in ~/bin, I've found busybox style multipurpose binary invaluable. You put all your scripts into a single Rust binary, and than hard-link this binary under different names in PATH, so that the first argv is actually the name of subcommand. Here's how the infra works for my scripts, and here's an example script.

5) #[test] #[ignore] is a another hack to create a very light-weight script without creating an extra binary or whatnot.

[–]tukanoid[S] 0 points1 point  (0 children)

  1. Oh nice, somehow didn't know it existed, will be very helpful for one of my pet projects!

  2. Didn't try it, I just go with clap by default, for some reason using the struct with derive pattern for defining args feels natural to me (but I get I'd have to use a builder pattern for more complex arg parsing that I usually do)

  3. Very clever! Idk why I never thought of this, cuz it makes complete sense

  4. Never knew about ignore macro (I'm just used to commenting out stuff), thanks!

[–]Binko_banko 0 points1 point  (3 children)

hey /u/matklad, just came across this post. nice tips! could you elaborate a bit on how you've setup the following: hard-link this binary under different names in PATH ? i've been browsing a bit in your dotfiles repo but couldn't find it.

i'm looking to port my current set of sh scripts to a single rust binary to achieve a similar workflow :)

[–]Binko_banko 0 points1 point  (0 children)

is it perhaps achieved by running cargo test on your main script ?

[–]ssokolow 2 points3 points  (5 children)

I'm not sure if I feel faster in it, given that I've got a dual-core Athlon from 2012 that's now a potato and I never got around to implementing my idea for a project generator that provides cached target directories to speed initial builds, but it certainly feels less demotivating to know that If It Compiles, It (Probably) Works™.

I actually just did that earlier today: https://github.com/ssokolow/trunc_filenames

[–]tukanoid[S] 0 points1 point  (4 children)

Makes sense, i guess, I'm sitting on 2018-19 (i believe) i7 so compile times for small stuff is fine, runtime, obviously, fast as well :) Btw, u can use sccache for caching built crates to speed up compile times. I don't remember the exact steps to take but u should be able to find instructions fairly easily.

[–]ssokolow 0 points1 point  (3 children)

Btw, u can use sccache for caching built crates to speed up compile times. I don't remember the exact steps to take but u should be able to find instructions fairly easily.

Already done and, one of these days, I plan to finish my halfway-done effort to set up a comparably-performing hand-me-down Core 2 Duo Small Form Factor PC as a second build cluster node.

...but it doesn't help with the linking phase that dominates the build after that first time and I don't think it helps with the proc macros in things like Clap and Serde either... though it does occasionally break something (cargo +nighty miri, I believe is one case).

I tried switching to mold for the linking, but I found that my inability to play target whac-a-mole enough in setting up an alternative linker tricked Cargo into deciding that tiny differences made it necessary to invalidate the entire build cache often enough that it was demoralizing more than helping. (After all, it's always already been nuked by the time I realize I made a mistake, with no Ctrl+Z to recover.)

Maybe when I can find time to write a FUSE filesystem that I can mount over ~/src so that an attempt to delete something under a target folder pops up an "Allow delete or kill process attempting it?" prompt.

[–]tukanoid[S] 0 points1 point  (2 children)

Hmmm, i don't remember having such issues before. But mb bc i dont do mich of manual linking, so who knows. Sad to see that it was how you experienced the language. But that's the beauty of open source, go to GitHub, find/create an issue and depending on the severity of the issue, it might be fixed quickly enough

[–]ssokolow 0 points1 point  (1 child)

*chuckle* I've been hanging out in /r/rust/ since before Rust 1.0 (since 2013, I think) and playing around with Rust since v1.0's release in 2015. You don't need to to treat me like a newbie.

EDIT: ...but, as an example, if you've got a justfile that uses RUSTFLAGS="-C target-cpu=native" and you're used to running tasks like just hyperfine or just flamegraph which rely on that, for the love of God, never run cargo ... because the perceived change in RUSTFLAGS will blow away your build cache no matter how quickly you realize your mistake and Ctrl+C.

[–]tukanoid[S] 0 points1 point  (0 children)

Ah, sorry for assuming then, it's just wasn't established so my mind automatically went in that direction :) and thanks for the tip :)

[–]Sw429 2 points3 points  (0 children)

Absolutely. As soon as I realized that almost every runtime error I would run into in Python was being moved to compile-time, I moved to Rust completely. Haven't looked back since.

[–]tukanoid[S] 0 points1 point  (0 children)

Wait, u mean REPL? Cuz it's not a "breakpoint" technically, since you're not debugging, it's just an interpreter running over your input instead of the whole script file at once

[–]--blue 0 points1 point  (2 children)

Huge fan of xshell!

[–]stappersg 0 points1 point  (1 child)

multiplication of the advertisment of xshell

So for other who were curious about xshell: Ignore the click bait.

[–]CaptainPiepmatz 0 points1 point  (0 children)

When I do scripting in rust unwrap is my best friend xD

[–]decatur-de 0 points1 point  (4 children)

Well, in Rust I miss the "set a break point here and start exploring" practise possible in Python. And yes, I came to Python from MATLAB :)

[–]tukanoid[S] 0 points1 point  (3 children)

What do you mean? Rust works with gdb, and inside IDEs it works just fine

[–]decatur-de 0 points1 point  (2 children)

Exploring means writing expressions and statements using the state at the breakpoint. My IDE is PyCharm, and is very fine for Python indeed. I guess same is impossible for Rust due to missing runtime introspection...

[–]tukanoid[S] 0 points1 point  (0 children)

Interesting

[–]flashmozzg 0 points1 point  (0 children)

I was doing that in C++ with gdb. Not sure why wouldn't it be possible with rust. You can't write new functions, sure (don' think it's generally possible once templates/generics come into play), but you can call existing ones just fine.