tezvyn:

Shadowing in Go and Rust: idioms, bugs, and if-block scoping

AI-drafted, machine-checkedSource: doc.rust-lang.orgadvanced

Tests lexical scoping in Go and Rust. Strong answers show Go's := narrowing and Rust's let rebinding, warn that Go's if := scopes across both branches, and contrast that with Rust's block-local let. Red flag: calling shadowing mutation.

WHAT THIS TESTS: Lexical scoping discipline, idiomatic use of shadowing for type refinement, and awareness of footguns in two statically typed languages. The interviewer wants to see if you understand that shadowing is not mutation but name rebinding, and whether you know the exact scope boundaries in Go's if initializer syntax versus Rust's block expressions.

A GOOD ANSWER COVERS: First, an idiomatic Go example such as using := inside an if initializer to narrow a type, like if data, ok := raw.(string); ok where the new binding exists for the if and else blocks. Second, an idiomatic Rust example such as let spaces = " "; let spaces = spaces.len(); where shadowing lets you reuse a name after transforming its type from string slice to usize. Third, a bug scenario in Go where using := inside a nested block accidentally shadows an outer variable of the same name, causing the outer variable to retain its zero value while the programmer thinks they mutated it. Fourth, the scoping distinction: in Go, an if statement with a short variable declaration creates bindings visible in every branch of that if statement but not outside it, whereas in Rust a let inside a standard block is strictly confined to that block and does not leak into sibling branches.

COMMON WRONG ANSWERS: Claiming that shadowing is just syntactic sugar for mutability. In Rust, shadowing with let creates an entirely new binding and allows changing the type, which mut does not permit. In Go, saying that := always mutates an existing variable; it only does so if all variables on the left already exist in the same scope, otherwise it creates new ones and shadows. Asserting that Go's if initializer variables are available after the if statement ends. Confusing Rust's if let pattern matching with standard block shadowing.

LIKELY FOLLOW-UPS: How does Rust's ownership model interact with shadowing when the new binding has a different type or lifetime? When is it better to use a new variable name instead of shadowing in production code? How do Go's package-level scope rules interact with short variable declarations in multi-file packages? Can you shadow a package import in Go, and why would that be dangerous?

ONE CONCRETE EXAMPLE: In Go, imagine parsing an integer with n, err := strconv.Atoi(input). If you later write if n, err := strconv.Atoi(otherInput); err == nil { ... }, the inner n shadows the outer n only within the if and else blocks. If you instead place n, err := inside the if body without the initializer, you might accidentally shadow or redeclare depending on what is already in scope, leading to a subtle bug where the outer n is unchanged after the block exits. In Rust, writing a block like let x = 5; let x = x + 1; is safe because the second x is block-local and the compiler tracks them as distinct bindings with no runtime confusion.

Read the original → doc.rust-lang.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.