Lab 1: Systems Programming Languages (C Analysis)
Key Findings
- Low-level Control: C provides direct memory manipulation and precise control over data layout
- Pointer Flexibility: C’s pointer abstraction allows for zero-copy data processing by casting buffers to different types
- Type Casting: C permits bit-level manipulation and reinterpretation of data through type casting
- Memory Management: Manual memory management gives control but creates responsibility for allocation/deallocation
- Undefined Behavior: C has 193 documented types of undefined behavior, making security critical code difficult
Strengths of C
- Direct memory manipulation without copying
- Precise control over data layout (bit fields, padding)
- Low overhead operations with predictable performance
- Transparent performance characteristics (no hidden costs)
- Direct access to hardware resources
Weaknesses of C
- Memory safety issues (buffer overflows, use-after-free)
- Manual memory management burden
- Type safety limitations allow memory corruption
- No validation of assumptions during type casting
- Error-prone string handling
Lab 2: Energy Efficiency in Programming Languages
Key Findings
- Energy Efficiency Hierarchy: Systems languages like C and Rust tend to be more energy-efficient than managed languages like Java and Python
- Contributing Factors:
- Runtime overhead (garbage collection, JIT compilation)
- Memory indirection levels
- CPU instruction efficiency
- Memory footprint
Energy Consumption Considerations
-
Device Usage Profiles:
- Server applications: Language efficiency has significant impact
- Mobile/IoT devices: Sleep/wake efficiency matters more than language
- Rarely-run applications: Algorithmic efficiency dominates language choice
-
Embodied Carbon:
- Manufacturing typically accounts for 50-80% of a device’s lifetime carbon footprint
- Extending device lifespan through efficient software has greater impact than minor runtime efficiency
-
Application Type Impact:
- Compute-intensive applications: Language choice matters significantly
- I/O-bound applications: Language efficiency has minimal impact
- System architecture decisions usually have greater impact than language choice
Lab 3: Introduction to Rust Programming
Key Concepts
Variables and Types
- Variables are immutable by default (
let x = 5) - Mutability must be explicitly declared (
let mut x = 5) - Type inference with static type checking
- Primitive types:
i8/u8throughi128/u128,f32,f64,bool,char(Unicode) - Compound types: tuples, arrays (fixed size, bounds-checked)
Functions
- Explicit type annotations required for parameters and return values
- Implicit return of final expression (no semicolon)
- Distinction between expressions (return values) and statements (end with semicolons)
Control Flow
- If-else expressions can return values
- Loop constructs:
loop,while,for - Pattern matching with
matchexpressions
Structures and Methods
- Named fields via
struct - Tuple structs with unnamed fields
- Unit-like structs with no fields
- Methods implemented via
implblocks
Enums and Pattern Matching
- Enums can contain data of different types
- Pattern matching with
matchis exhaustive - Standard library provides
Option<T>andResult<T, E>enums
Lab 4: Types and Traits in Rust
Key Concepts
Type-Driven Development
- Design around types rather than control flow
- Let compiler verify design consistency
- Gradually refine implementation while maintaining type safety
- Debug designs at compile time, not at runtime
Design Patterns
-
Specific Numeric Types:
- Replace generic numeric types (
i32) with domain-specific ones (Temperature,UserId) - No runtime overhead in Rust when using newtype pattern
- Prevents errors like mixing incompatible units
- Replace generic numeric types (
-
Enums for Alternatives:
- Avoid “string typing” and boolean flags
- Make intent clear through type names
- Enable exhaustiveness checking
State Machines
-
Enum-based approach:
- Define states and events as enums
- Create transition function mapping (state, event) to new state
- Good for complex state machines with many transitions
-
Struct-based approach:
- One struct per state
- State transitions as functions that consume self
- Methods only available in appropriate states
- Leverages ownership to enforce transitions
-
Phantom Types:
- Use zero-sized marker types to track states
- Type parameters indicate state without runtime cost
- Compile-time enforcement of state transitions
Traits
- Define shared functionality across different types
- Similar to interfaces in other languages but more flexible
- Key to generic programming in Rust
- Enable abstraction without inheritance
Lab 5: Ownership, Pointers, and Memory
Key Concepts
Ownership Rules
- Each value has a single owner
- When owner goes out of scope, value is dropped
- Ownership can be transferred (moved) between variables
References and Borrowing
- At any time, you can have either:
- One mutable reference (
&mut T) - Any number of immutable references (
&T)
- One mutable reference (
- References must always be valid (no dangling references)
Lifetimes
- Lifetimes ensure references are valid for as long as they’re used
- The lifetime annotation
'aspecifies how long references need to live - Most lifetimes are inferred, but sometimes explicit annotations are needed
State Machines via Ownership
- Method consuming
selfenforces state transitions - Taking ownership prevents access after state change
- Return new state object to move to next state
- Compiler enforces correct state transition sequence
Memory Management
-
Deterministic resource cleanup through RAII pattern
-
Box<T> for heap allocation with single owner
-
Stack vs. heap considerations for performance
Lab 6: Closures and Concurrency
Key Concepts
Closures
- Anonymous functions that capture their environment
- Capture variables by:
- Reference (
&T) - implementsFntrait - Mutable reference (
&mut T) - implementsFnMuttrait - Value (
T) - implementsFnOncetrait
- Reference (
- Use
movekeyword to take ownership of captured variables
Threads
- Created with
std::thread::spawn(|| { /* code */ }) - Join handles wait for thread completion
- Must use
moveclosures to transfer ownership to threads
Message Passing
- Channels for thread communication:
mpsc::channel() - Sender and receiver ends can be separated
- Ownership is transferred when sending values
- Multiple producers possible by cloning sender
Thread Safety
- Rust prevents data races at compile time
- Shared ownership with
Arc<T>(Atomic Reference Counting) - Synchronization with
Mutex<T>orRwLock<T> - Marker traits like
SendandSyncenforce thread safety
Safe Concurrency Patterns
- Worker pools with channels
- Thread-safe data structures
- Actor model implementations
- Data parallelism
Lab 7: Coroutines and Asynchronous Programming
Key Concepts
Coroutines
- Functions that can pause execution and resume later
- Represented as state machines that maintain their state between calls
- Cooperative multitasking model (yield control explicitly)
Futures in Rust
- Represent asynchronous computations that will complete later
- Implement the
Futuretrait withpollmethod - Don’t execute on their own - driven by a runtime
Async/Await
async fntransforms functions to return futuresawaitsuspends execution until a future completes- Compiler transforms code into state machine implementation
- Makes asynchronous code look sequential
Asynchronous Runtimes
- Rust doesn’t include a built-in runtime (unlike Python)
- External libraries provide runtimes (Tokio, async-std)
- Event loops manage future execution and I/O multiplexing
#[tokio::main]macro sets up the runtime
Comparison with Threads
- Advantages:
- Lower memory overhead (no thread stacks)
- More scalable for I/O-bound workloads
- Can handle thousands of concurrent operations with few threads
- Disadvantages:
- Runtime ecosystem fragmentation
- Blocking operations stall all tasks in a thread
- Debugging can be more difficult
- Not suited for CPU-bound tasks
Comparative Analysis: Memory Management Approaches
Manual Memory Management (C)
- Pros: Precise control, no runtime overhead
- Cons: Error-prone, security vulnerabilities, cognitive burden
Reference Counting (C++, Objective-C)
- Pros: Deterministic cleanup, easy to understand
- Cons: Runtime overhead, can’t handle cyclic references
Garbage Collection (Java, Python)
- Pros: Eliminates memory leaks, handles cycles
- Cons: Unpredictable pauses, higher memory usage
Region-Based Memory Management (Rust)
- Pros: Deterministic, no runtime overhead, prevents common errors
- Cons: Learning curve, restrictions on data structures
Security Considerations
Memory Safety Issues
- Buffer overflows
- Use-after-free
- Null pointer dereferences
- Double frees
- Data races
Type System Benefits for Security
- Strong typing prevents type confusion
- Exhaustive pattern matching ensures error handling
- Ownership prevents use-after-free vulnerabilities
- Borrowing rules prevent data races
- Semantic typing for different validation states
Parser Security
- Formal grammar specifications
- Input validation
- Parser combinator libraries
- Zero-copy parsing safely (with ownership)
Concurrency Models
Shared State with Locks
- Pros: Familiar model, efficient for some workloads
- Cons: Deadlocks, race conditions, doesn’t compose well
Message Passing
- Pros: No shared state, easier reasoning, scales to distributed systems
- Cons: Potential message ordering issues, copy overhead
Transactional Memory
- Pros: Composable, no deadlocks, isolates failures
- Cons: Implementation complexity, limited language support
Asynchronous Programming
- Pros: Efficient I/O multiplexing, familiar control flow
- Cons: “Colored functions” problem, potential starvation
Core Themes Across Labs
-
Safety vs. Control Tradeoff: Systems programming traditionally sacrificed safety for control; modern languages aim for both
-
Type Systems as Documentation: Types encode design constraints and make assumptions explicit
-
Ownership as a Unifying Concept: Ownership simplifies memory management, concurrency, and state machines
-
Zero-Cost Abstractions: Modern systems languages aim for high-level abstractions with no runtime cost
-
Compiler as Ally: Moving checks from runtime to compile time improves both safety and performance
-
Explicit over Implicit: Making costs and side effects explicit leads to more maintainable systems