Rust's memory model can have profound impacts on the way programs are designed and written. Here are a few of the tricks I have picked up over the years.

Initialize, then use

First, a little bit of code that does not work:

let values = io::stdin().lock().lines()
    .filter_map(|line| line.ok().and_then(|line| {
        line.parse::<i32>().ok()
    }));

This seems fine in theory. We take a handle to stdin, lock it, read by lines, and parse those lines into numbers. Catch is, locking stdin takes a reference to stdin, which will then be "out of scope" and therefore dead as a doornail by the time we get around to executing our filter map and parsing our input. This is annoying as hell; the solution is easy, but non-obvious for a beginner, and it's also longer.

let handle = io::stdin();
let values = handle.lock().lines()
    .filter_map(|line| line.ok().and_then(|line| {
        line.parse::<i32>().ok()
    }));

Now our handle on stdin exists as distinct from the iterator adaptor consuming stdin and the borrow checker is cool with everything. If we carry this a step farther, as I usually do, it often comes out looking like this...

use std::io::{self, BufRead, Stdin};

fn main() {
    let handle = io::stdin();
    let values = read_values(handle);
}

fn read_values(input: Stdin) -> Vec<i32> {
    input.lock().lines()
        .filter_map(|line| {
            line.ok().and_then(|line| line.parse().ok())
        })
        .collect()
}

...Which is a specific case of a general pattern that I find to be very common in rust: define resources in a lower stack frame, then use them in a higher stack frame. Or, like the title of this section says, "initialize, then use."

Your program is an object

Not literally. I mean, your program is code running on a computer, but the point is that your program is not this thing that starts with main. In C#, a program is literally an instance of a class, right? The framework instantiates your class and then calls the method marked as the entry point (or, you know—Main)... That's basically what you need to do in Rust. Here's an example.

Say you want to process a set of files from the internet. Maybe your cousin has a crappy old website where your birthday pictures are stored and you want to navigate through it and download all the pictures so that he can stop paying forty dollars a month to Flaibainaight IT. The poor bastard. Anyway, this task requires a web client and probably some regular expressions. Here's how you might architect it in a garbage-collected language:

static string url = "whatever";
static Client client = InitClient();
static Regex linkDetector = InitLinkDetector();
static Regex photoDetector = InitPhotoDetector();

static void Main() {
    var root = client.GetRoot(url);
    foreach (var link in Links(root)) {
        var content = client.GetLink(link);
        var pictures = Pictures(content);
        
        ...
    }
}

Simple, but not that simple in Rust. The complication is that, in the hypothetical example above, we depend on the Links and Pictures methods that consume both the client and the regular expressions from above. They are accessible because they are static and in scope for the methods in question, but there really is no such thing in Rust. You can use a crate like lazy_static, but that introduces a measure of overhead, and it just feels a little bit clunky to have to introduce that crate into practically every damn thing you do.

The alternative is to consider that your program can work exactly the same way in Rust that it does in C#: That is, it can be an instance of something. Check this out...

struct BirthdayPictureThief {
    client: Client,
    link_regex: Regex,
    picture_regex: Regex,
}

impl BirthdayPictureThief {
    fn get_root(&self, url: &str) -> String { ...
    fn get_link(&self, url: &str) -> String { ...
    fn get_links(&self, content: &str) -> Vec<Link> { ...
    fn get_pictures(&self, content: &str) -> io::Result<()> { ...

In main, you initialize your BirthdayPictureThief and then because each of your methods is defined on the thief, each method has access to the same context it would have in our hypothetical C# version. The alternative, which is painful, is to pass every damn thing as a parameter to every damn function—which gets really fun when you're halfway done and realize you forgot one parameter that you need at the very top of the stack and you have to go back and pass it through four or five other layers to get it there.

It's not uncommon, and neither is it a bad idea, to take this a step further and create a program object that you call like program.run() in main, and then its return value is just a Result<(), ProgramError> so that you can report on any failures in main or just close if everything went ok.

Anyway, this brings me to the last thing I feel I should mention just now...

It's a stack

The whole borrow check/memory model/reference thing seems mysterious (because, really, it is), but it can help you to consider that it's really just a way to think about how the stack and the heap work. I know, that's blasphemy, and that's something that you've never thought about before if you're anything like me, but it's not that complicated.

Your stack is your desk. If you put a piece of paper on your desk, that's a stack frame. Now, imagine you write your name on there.

fn main() {
    let name = String::from("John"); // best name ever
}

With me so far? Ok, now, you want to make a document that has your name in some kind of message. Here's a crazy idea! Take your message and write it on another piece of paper, but then cut a hole in that paper and lay it over the top of the first.

fn greet(name: &str) {
    println!("Hello, {}!", name);
}

fn main() {
    let name = String::from("John"); // best name ever
    greet(&name);
}

Now, this second sheet of paper can "reference" something from the first sheet, but not vice versa. Because, you know, obviously, to get to the first sheet, you have to throw away the second. That's how the stack works, son! So, while the above example works, this one does not:

fn name() -> &str {
    &String::from("John")
}

fn main() {
    let name = name();
    println!("Hello, {}!", name);
}

...Because you're going to lay down the sheet with the name on it and then pick it up again before you get around to reading off your greeting!

For what it's worth, the compiler will bark incessantly at you for about three different kinds of errors, here, but the point is that if you think of your program as a physical stack of items, the whole reference/borrowing/ownership thing can sometimes make a lot of sense.