-
-
Save rust-play/c16f0bc7e1a53cbcd44e58c891f6a4fd to your computer and use it in GitHub Desktop.
Code shared from the Rust Playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
fn main() { | |
// COMPLETE PROGRAM: Demonstrating scope trees and lifetime relationships | |
println!("=== Scope Tree Visualization ==="); | |
scope_tree_example(); | |
println!("\n=== Lifetime Elision Rules ==="); | |
elision_examples(); | |
println!("\n=== When Elision Fails ==="); | |
explicit_lifetime_examples(); | |
println!("\n=== Multiple Lifetime Parameters ==="); | |
multiple_lifetime_examples(); | |
} | |
fn scope_tree_example() { | |
// CONCEPT: Nested scopes create a tree structure | |
// Each scope level is like a generation in a family tree | |
// ROOT SCOPE: Grandparent level | |
let grandparent = String::from("Ancient wisdom passed down"); | |
println!("Grandparent created: {}", grandparent); | |
{ // CHILD SCOPE 1: Parent level | |
// CONCEPT: Child scopes can borrow from parent scopes | |
let parent = &grandparent; // Borrows from grandparent | |
println!("Parent borrows: {}", parent); | |
{ // GRANDCHILD SCOPE: Child level | |
// CONCEPT: Children can borrow from their parents | |
// This creates a chain: grandchild -> parent -> grandparent | |
let child = &parent; // Borrows from parent (who borrowed from grandparent) | |
println!("Child borrows: {}", child); | |
// CONCEPT: All three generations coexist safely | |
// The borrow checker ensures no conflicts | |
println!("Three generations: '{}', '{}', '{}'", | |
grandparent, parent, child); | |
// CONCEPT: The rule - children cannot outlive their parents | |
// `child` must be dropped before `parent` | |
// `parent` must be dropped before `grandparent` | |
} // Child scope ends - `child` is dropped here | |
// CONCEPT: Parent can still be used after child is gone | |
println!("Parent survives child: {}", parent); | |
} // Parent scope ends - `parent` is dropped here | |
// CONCEPT: Grandparent survives all descendants | |
println!("Grandparent survives all: {}", grandparent); | |
// CONCEPT: Why this works | |
// The compiler tracks the lifetime hierarchy: | |
// - grandparent: lives for entire function | |
// - parent: lives for middle scope, borrows from grandparent | |
// - child: lives for inner scope, borrows from parent | |
// No reference outlives its source! | |
} | |
// CONCEPT: Lifetime elision - the compiler's auto-complete for lifetimes | |
// Rule 1: Each parameter gets its own lifetime | |
fn process_single_input(input: &str) -> String { | |
// WHAT YOU WRITE: No explicit lifetimes | |
// WHAT COMPILER SEES: fn process_single_input<'a>(input: &'a str) -> String | |
// CONCEPT: Since we're returning owned data (String), | |
// no lifetime relationship exists between input and output | |
input.to_uppercase() | |
} | |
// Rule 2: Single input reference → output gets same lifetime | |
fn get_first_word(text: &str) -> &str { | |
// WHAT YOU WRITE: No explicit lifetimes | |
// WHAT COMPILER INFERS: fn get_first_word<'a>(text: &'a str) -> &'a str | |
// CONCEPT: The output is a slice of the input | |
// So the output must live as long as the input | |
text.split_whitespace().next().unwrap_or("") | |
} | |
// Rule 3: Method with &self → output gets self's lifetime | |
struct TextAnalyzer { | |
content: String, | |
} | |
impl TextAnalyzer { | |
fn new(content: String) -> Self { | |
Self { content } | |
} | |
// WHAT YOU WRITE: No explicit lifetimes | |
fn get_content(&self) -> &str { | |
// WHAT COMPILER INFERS: fn get_content<'a>(&'a self) -> &'a str | |
// CONCEPT: The returned slice borrows from self | |
// The slice can't outlive the TextAnalyzer instance | |
&self.content | |
} | |
fn get_word_count(&self) -> usize { | |
// CONCEPT: No lifetime issues here - returning owned data | |
self.content.split_whitespace().count() | |
} | |
// CONCEPT: Multiple borrows from self are fine | |
fn get_summary(&self) -> (usize, &str) { | |
// WHAT COMPILER INFERS: fn get_summary<'a>(&'a self) -> (usize, &'a str) | |
let word_count = self.get_word_count(); | |
let first_word = self.get_content().split_whitespace().next().unwrap_or(""); | |
(word_count, first_word) | |
} | |
} | |
fn elision_examples() { | |
// CONCEPT: Testing elision rules in practice | |
let data = "Hello world from Rust"; | |
// Rule 1: Single input, owned output | |
let processed = process_single_input(data); | |
println!("Processed (owned): {}", processed); | |
// Rule 2: Single input, borrowed output | |
let first_word = get_first_word(data); | |
println!("First word (borrowed): {}", first_word); | |
// Rule 3: Method with &self | |
let analyzer = TextAnalyzer::new(data.to_string()); | |
let content = analyzer.get_content(); | |
let (count, first) = analyzer.get_summary(); | |
println!("Content: {}", content); | |
println!("Summary: {} words, starts with '{}'", count, first); | |
// CONCEPT: All borrows are valid because analyzer outlives all references | |
} | |
// CONCEPT: When elision fails - compiler needs explicit help | |
fn explicit_lifetime_examples() { | |
let text1 = "Hello world"; | |
let text2 = "Rust programming"; | |
// CONCEPT: When multiple inputs exist, compiler can't guess | |
// which input the output should be tied to | |
let longer = choose_longer_explicit(&text1, &text2); | |
println!("Longer text: {}", longer); | |
// CONCEPT: Different lifetime requirements | |
let first_part = get_first_part_explicit(&text1, &text2); | |
println!("First part of first input: {}", first_part); | |
} | |
// CONCEPT: Explicit lifetime when compiler can't infer | |
fn choose_longer_explicit<'a>(s1: &'a str, s2: &'a str) -> &'a str { | |
// CONCEPT: Both inputs must live as long as the output | |
// The 'a lifetime ties all three together | |
// This means: "s1 and s2 must both live at least as long as the returned reference" | |
if s1.len() > s2.len() { | |
s1 // Could return either input | |
} else { | |
s2 // So both must live long enough | |
} | |
} | |
// CONCEPT: Different lifetimes when only one input matters | |
fn get_first_part_explicit<'a, 'b>(primary: &'a str, _secondary: &'b str) -> &'a str { | |
// CONCEPT: Output only depends on 'primary' | |
// 'secondary' can have a completely different lifetime | |
// This is more flexible than forcing both to have the same lifetime | |
&primary[0..primary.len().min(5)] // Return first 5 chars of primary | |
} | |
fn multiple_lifetime_examples() { | |
// CONCEPT: Testing functions with multiple lifetime parameters | |
let long_lived = String::from("This is a long-lived string"); | |
{ // Shorter scope | |
let short_lived = String::from("Short"); | |
// CONCEPT: Both inputs available, function works | |
let result1 = choose_longer_explicit(&long_lived, &short_lived); | |
println!("Result 1: {}", result1); | |
// CONCEPT: Only primary input matters for this function | |
let result2 = get_first_part_explicit(&long_lived, &short_lived); | |
println!("Result 2: {}", result2); | |
// CONCEPT: result2 can outlive short_lived because it only | |
// depends on long_lived (due to different lifetime parameters) | |
} // short_lived dropped here | |
// This would work for result2 but not result1: | |
// println!("After scope: {}", result2); // Would be OK | |
// println!("After scope: {}", result1); // Would be ERROR | |
} | |
// CONCEPT: Complex lifetime relationships | |
struct DataProcessor<'data> { | |
// CONCEPT: Struct that holds a reference to external data | |
// The struct cannot outlive the data it references | |
source: &'data str, | |
processed_count: usize, | |
} | |
impl<'data> DataProcessor<'data> { | |
fn new(source: &'data str) -> Self { | |
// CONCEPT: The processor borrows the source data | |
// It doesn't own the data, just processes it | |
Self { | |
source, | |
processed_count: 0, | |
} | |
} | |
fn process_chunk(&mut self, start: usize, len: usize) -> Option<&'data str> { | |
// CONCEPT: Return a slice that lives as long as the original source | |
// The lifetime 'data ensures the slice is valid | |
if start + len <= self.source.len() { | |
self.processed_count += 1; | |
Some(&self.source[start..start + len]) | |
} else { | |
None | |
} | |
} | |
fn get_stats(&self) -> (usize, usize) { | |
// CONCEPT: Returning owned data - no lifetime constraints | |
(self.source.len(), self.processed_count) | |
} | |
} | |
// CONCEPT: Advanced lifetime demonstration | |
fn demonstrate_processor() { | |
println!("\n=== Advanced Lifetime Relationships ==="); | |
let source_data = "The quick brown fox jumps over the lazy dog"; | |
// CONCEPT: Create processor that borrows source_data | |
let mut processor = DataProcessor::new(source_data); | |
// CONCEPT: Process chunks - returned slices borrow from original data | |
if let Some(chunk1) = processor.process_chunk(0, 9) { | |
println!("Chunk 1: '{}'", chunk1); | |
} | |
if let Some(chunk2) = processor.process_chunk(10, 5) { | |
println!("Chunk 2: '{}'", chunk2); | |
} | |
// CONCEPT: Get statistics - owned data, no lifetime constraints | |
let (total_len, processed_count) = processor.get_stats(); | |
println!("Processed {} chunks from {} characters", processed_count, total_len); | |
// CONCEPT: All chunks and processor must be dropped before source_data | |
// The borrow checker enforces this automatically | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn test_elision_rules() { | |
// CONCEPT: Testing that elision works as expected | |
let input = "test input"; | |
// Rule 1: owned output | |
let owned = process_single_input(input); | |
assert_eq!(owned, "TEST INPUT"); | |
// Rule 2: borrowed output tied to input | |
let borrowed = get_first_word(input); | |
assert_eq!(borrowed, "test"); | |
// Rule 3: method with &self | |
let analyzer = TextAnalyzer::new(input.to_string()); | |
let content = analyzer.get_content(); | |
assert_eq!(content, input); | |
} | |
#[test] | |
fn test_explicit_lifetimes() { | |
// CONCEPT: Testing explicit lifetime annotations | |
let s1 = "short"; | |
let s2 = "much longer string"; | |
let result = choose_longer_explicit(s1, s2); | |
assert_eq!(result, s2); // s2 is longer | |
let first_part = get_first_part_explicit(s1, s2); | |
assert_eq!(first_part, "short"); // First 5 chars of s1 | |
} | |
#[test] | |
fn test_data_processor() { | |
// CONCEPT: Testing struct with lifetime parameters | |
let data = "Hello Rust World"; | |
let mut processor = DataProcessor::new(data); | |
let chunk = processor.process_chunk(0, 5).unwrap(); | |
assert_eq!(chunk, "Hello"); | |
let (len, count) = processor.get_stats(); | |
assert_eq!(len, 16); | |
assert_eq!(count, 1); | |
} | |
} | |
// Run the demonstrations | |
fn run_demonstrations() { | |
demonstrate_processor(); | |
} | |
// CONCEPT: Entry point that ties everything together | |
// This shows the complete flow of lifetime concepts |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment