Skip to content

Commit

Permalink
Add "Ownership and Lifetimes" lab
Browse files Browse the repository at this point in the history
  • Loading branch information
jolisper committed Apr 2, 2024
1 parent 58b99dc commit f011abc
Show file tree
Hide file tree
Showing 6 changed files with 309 additions and 4 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ members = [
"week-1/Lesson 1 - Rust Sequences and Maps/vecdeque-fruit-salad",
"week-1/Lesson 1 - Rust Sequences and Maps/linked-list-fruit-salad",
"week-1/Lesson 1 - Rust Sequences and Maps/cli-salad",
"week-1/Lesson 1 - Rust Sequences and Maps/rust-collections-docs", "week-1/Lesson 1 - Rust Sequences and Maps/lesson-reflection", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/russian-troll-tweets", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/when-use-rust-set", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/rust-iterators", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/neo4j-data-science-lib", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/graph-centrality-ufc", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/hashset-fruit", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/btreeset-fruit", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/binaryheap-fruit", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/pagerank", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/lisbon-shortest-path", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/community-detection", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/graph-visualize", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/w1l2-lesson-reflection", "week-1/Final Week-Reflection/fully-connected-graph", "week-1/Final Week-Reflection/w1l2-final-reflection", "week-2/Lesson 1 - Rust Safety and Security Features/mutable-fruit-salad", "week-2/Lesson 1 - Rust Safety and Security Features/cli-customize-fruit-salad", "week-2/Lesson 1 - Rust Safety and Security Features/data-race", "week-2/Lesson 1 - Rust Safety and Security Features/meet-safe-and-unsafe",
"week-1/Lesson 1 - Rust Sequences and Maps/rust-collections-docs", "week-1/Lesson 1 - Rust Sequences and Maps/lesson-reflection", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/russian-troll-tweets", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/when-use-rust-set", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/rust-iterators", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/neo4j-data-science-lib", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/graph-centrality-ufc", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/hashset-fruit", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/btreeset-fruit", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/binaryheap-fruit", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/pagerank", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/lisbon-shortest-path", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/community-detection", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/graph-visualize", "week-1/Lesson 2 - Rust Sets, Graphs and Misc Data Structures/w1l2-lesson-reflection", "week-1/Final Week-Reflection/fully-connected-graph", "week-1/Final Week-Reflection/w1l2-final-reflection", "week-2/Lesson 1 - Rust Safety and Security Features/mutable-fruit-salad", "week-2/Lesson 1 - Rust Safety and Security Features/cli-customize-fruit-salad", "week-2/Lesson 1 - Rust Safety and Security Features/data-race", "week-2/Lesson 1 - Rust Safety and Security Features/meet-safe-and-unsafe", "week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes",
]
resolver = "2"
9 changes: 6 additions & 3 deletions week-2/Lesson 1 - Rust Safety and Security Features/Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
SHELL := /bin/bash
.PHONY: help all mutable-fruit-salad cli-customize-fruit-salad data-race meet-safe-and-unsafe
.PHONY: help all mutable-fruit-salad cli-customize-fruit-salad data-race meet-safe-and-unsafe ownership-lifetimes

help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

all: mutable-fruit-salad cli-customize-fruit-salad data-race meet-safe-and-unsafe ## Build all projects
all: mutable-fruit-salad cli-customize-fruit-salad data-race meet-safe-and-unsafe ownership-lifetimes ## Build all projects

mutable-fruit-salad: ## Build mutable-fruit-salad project
make -C "mutable-fruit-salad" clean test build
Expand All @@ -16,4 +16,7 @@ data-race: ## Build data-race project
make -C "data-race" clean test build

meet-safe-and-unsafe: ## Build meet-safe-and-unsafe project
make -C "meet-safe-and-unsafe" clean test build
make -C "meet-safe-and-unsafe" clean test build

ownership-lifetimes: ## Build ownership-lifetime project
make -C "ownership-lifetimes" clean test build
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "ownership-lifetimes"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
SHELL := /bin/bash
.PHONY: help clean lint format test doc build run bump

help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

clean: ## Remove all build artifacts
cargo clean

lint: ## Lint code
@rustup component add rustfmt 2> /dev/null
cargo clippy

format: ## Format code
@rustup component add rustfmt 2> /dev/null
cargo fmt

test: ## Run tests
cargo test

doc: ## Generate documentation
cargo doc --no-deps

bench: ## Run benchmarks
cargo bench

build: ## Build
cargo build

all: clean lint format test doc build ## Build and run

run: ## Run
cargo run

bump: ## Bump version
@echo "Current version: $$(cargo pkgid | grep -o '#.*' | cut -d# -f2)"
@read -p "Enter new version: " new_version && \
sed -i "s/version = \".*\"/version = \"$$new_version\"/" Cargo.toml && \
echo "Updated to new version: $$(cargo pkgid | grep -o '#.*' | cut -d# -f2)"
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
//! Reflection Questions:
//!
//! # What are the ownership rules in Rust?
//!
//! In Rust, the ownership system is governed by three main rules:
//!
//! 1. Each value in Rust has a single owner.
//! 2. There can only be one owner at a time.
//! 3. When the owner goes out of scope, the value will be dropped.
//!
//! These rules ensure memory safety and concurrency without the need for a
//! garbage collector.
//!
//! # How does ownership help manage memory safely in Rust?
//!
//! Ownership in Rust is a key concept that ensures memory safety by automatically
//! returning resources once their owners go out of scope. This process is
//! facilitated by the following principles:
//!
//! - **Exclusive Ownership**: Each value in Rust is owned by a single variable,
//! which ensures that only one owner can manage the memory of the value at any
//! given time.
//! - **Transfer of Ownership**: When ownership is transferred, the previous owner
//! can no longer access the value, preventing the use of invalid references.
//! - **Scope-Based Resource Management**: As soon as the owner goes out of scope,
//! Rust automatically calls the `drop` function for the value, releasing the
//! allocated memory back to the system.
//!
//! These ownership rules ensure that each piece of memory is adequately cleaned
//! up, preventing memory leaks and other common memory errors, without the need
//! for a garbage collector.
//!
//! # What is the difference between a deep copy and a shallow copy?
//!
//! The difference between a deep copy and a shallow copy lies in how they handle
//! the object's underlying data:
//!
//! - **Shallow Copy**: A shallow copy duplicates as little as possible. It copies
//! the outer structure of the object but not the inner data. Instead, it copies
//! the pointers or references to the data. Thus, the original data remains
//! shared between the copy and the original object.
//!
//! - **Deep Copy**: A deep copy, on the other hand, duplicates everything. It
//! creates a copy not just of the object's structure but also of all the data
//! it references. This means that the copied object is entirely independent of
//! the original, with no shared references to mutable data.
//!
//! Making a deep copy ensures that changes to the copy will not affect the
//! original object, while a shallow copy might lead to side effects if the
//! underlying data is modified. Rust will never automatically create “deep”
//! copies of your data. Therefore, any automatic copying can be assumed to
//! be inexpensive in terms of runtime performance. If we do want to deeply
//! copy heap data, not just the stack data, we can use a common method
//! called clone.
//! When you see a call to clone, you know that some arbitrary code is being
//! executed and that code may be expensive. It’s a visual indicator that
//! something different is going on.
//!
//! # What is the Copy trait, and when can it be used?
//!
//! The `Copy` trait in Rust is a special marker trait that can be implemented by
//! types whose values can be duplicated simply by copying their bits. This trait
//! is generally used for types that are stored entirely on the stack, not
//! involving any heap allocation. When a type implements the `Copy` trait, it
//! indicates to the compiler that a bitwise copy of the value is sufficient and
//! safe, without needing to transfer ownership.
//!
//! The `Copy` trait can be automatically derived for types that:
//!
//! - Do not contain non-`Copy` fields.
//! - Are scalar types like integers, floating-point numbers, and characters.
//! - Comprise fixed-size arrays of `Copy` types.
//! - Are tuples composed only of `Copy` types.
//!
//! Implementing the `Copy` trait allows for more flexible semantics as values can
//! be passed around by copy without adhering to the strict ownership and
//! borrowing rules that apply to non-`Copy` types. However, types that manage
//! resources beyond their own size, such as `String` or `Vec`, cannot implement
//! `Copy` because a simple bitwise copy would lead to multiple ownership of the
//! same heap data, violating Rust's ownership rules.
//!
//! Disscussion Prompts:
//!
//! # How does Rust's ownership model compare to garbage collection in other
//! languages? What are the tradeoffs?
//!
//! Rust's ownership model is a compile-time memory management system that
//! enforces strict rules about how memory and other resources are allocated and
//! deallocated, ensuring memory safety without the need for a garbage collector
//! (GC). Here are some key comparisons and tradeoffs:
//!
//! - **Predictability**: Rust's ownership model allows for deterministic
//! deallocation of resources, as opposed to the nondeterministic nature of GC
//! where the exact time of collection is not known. This predictability is
//! beneficial for performance-critical applications.
//!
//! - **Performance**: Rust's model can lead to better performance in some cases
//! because it avoids the runtime overhead associated with garbage collection.
//! However, this comes at the cost of increased complexity in the language's
//! rules, which the developer must understand and manage correctly.
//!
//! - **Memory Usage**: Rust's ownership approach can potentially lead to lower
//! memory usage since objects are deallocated immediately when no longer in
//! use, whereas GC systems might delay cleanup until the collector runs.
//!
//! - **Ease of Use**: Garbage-collected languages often provide a more
//! straightforward programming model since developers don't have to be as
//! concerned with the precise management of memory. This can lead to faster
//! development times at the expense of possible GC-induced latencies and
//! less explicit control over memory.
//!
//! - **Safety Guarantees**: Rust's ownership model provides strong compile-time
//! guarantees about memory safety, preventing common errors such as null
//! pointer dereferences, buffer overflows, and use-after-free bugs. Garbage
//! collection ensures that memory is not leaked, but it does not provide the
//! same level of safety against these other types of bugs.
//!
//! In conclusion, the choice between Rust's ownership model and garbage
//! collection depends on the needs of the application and the priorities of the
//! developer. While Rust's model is more complex and has a steeper learning
//! curve, it offers performance and safety benefits. On the other hand, garbage
//! collection simplifies development but may introduce performance penalties.
//!
//! # What ownership rules have you found most confusing when learning Rust? How
//! did you gain understanding?
//!
//! As an AI developed by Codeium, I don't experience confusion or learn like
//! humans. However, many new Rustaceans often find the following aspects of
//! ownership challenging:
//!
//! - **Borrowing Rules**: Rust's borrowing rules, which include mutable and
//! immutable references, and the concept that you can't have mutable and
//! immutable references to the same value at the same time, can be confusing.
//! Understanding borrowing comes with practice and learning to reason about
//! scopes and lifetimes.
//!
//! - **Lifetime Annotations**: Lifetimes are a way of telling the Rust compiler
//! how references relate to each other in terms of their validity periods.
//! They can be difficult to grasp initially, especially when working with
//! complex data structures or function signatures. Reading the Rust book,
//! experimenting with code, and understanding error messages helps to
//! demystify lifetimes.
//!
//! - **Ownership Transfer**: The concept of ownership transfer, especially when
//! using functions and passing values around, can lead to confusion about
//! when and where values are moved or copied. Writing more Rust code and
//! tracking ownership through function calls can clarify these rules.
//!
//! Gaining a deeper understanding of these rules often involves:
//!
//! - **Reading Extensively**: The Rust Book, official documentation, and
//! community resources provide comprehensive explanations and examples.
//!
//! - **Practicing with Real Code**: Building projects and working through
//! compiler errors helps solidify the concepts.
//!
//! - **Using Tools**: Tools like `cargo check` and the Rust Language Server
//! provide real-time feedback on code, which can accelerate the learning
//! process.
//!
//! - **Engaging with the Community**: Forums, chat groups, and other community
//! resources can provide support and clarification.
//!
//! It's important to remember that getting comfortable with Rust's ownership
//! model takes time and practice, and it's normal to find these concepts
//! challenging at first.
//!
//! # Why do you think Rust favors moves over deep copying by default? What are
//! the advantages?
//!
//! Rust favors moves over deep copying by default due to several advantages that
//! align with its goals of safety and performance:
//!
//! - **Efficiency**: Moving ownership is generally more efficient than deep
//! copying because it involves transferring a fixed amount of memory (the size
//! of a pointer) regardless of the size of the data being pointed to. This
//! avoids the overhead of duplicating potentially large or complex data
//! structures.
//!
//! - **Memory Safety**: By moving ownership of values, Rust ensures that only
//! one variable is responsible for managing the memory of a given resource at
//! a time. This eliminates issues like double free errors and data races that
//! can occur in languages that allow multiple variables to own the same data.
//!
//! - **Resource Management**: Moves help in deterministic and explicit resource
//! management. When a value is moved out of a scope, its destructor is called,
//! and the resources are freed immediately, which is not guaranteed with deep
//! copies.
//!
//! - **Encouragement of Immutable Patterns**: Rust's preference for moves
//! encourages a programming style where data is transformed rather than
//! modified in place, leading to more predictable and less error-prone code.
//!
//! - **Opt-in Cost**: Deep copying can still be performed when necessary, but
//! it's an explicit choice made by the programmer. This allows developers to
//! assess and opt-in to the associated performance costs when it makes sense
//! for their specific use case.
//!
//! In summary, moves are a default behavior in Rust because they offer a good
//! balance between performance and memory safety, while still allowing
//! developers to perform deep copies when needed.
//!
//! # How does ownership affect how you design and structure programs in Rust?
//! What changes compared to other languages?
//!
//! Ownership in Rust has a profound impact on the design and structure of
//! programs. Compared to languages without ownership semantics, several changes
//! and considerations are apparent:
//!
//! - **Data Ownership and Flow**: Rust programs are designed with a clear
//! understanding of which part of the code owns which data and how that data
//! flows between different parts of the program. This requires thinking ahead
//! about the lifetime of objects and the sharing of data.
//!
//! - **Immutability by Default**: Rust encourages immutability, so data
//! structures are often designed to be immutable unless there's a specific
//! need for mutability. This leads to safer and more predictable code.
//!
//! - **Error Handling**: Rust's ownership model affects error handling, leading
//! to a preference for returning `Result` types over throwing exceptions. This
//! ensures that even in the case of an error, all data is properly cleaned up.
//!
//! - **Concurrent Programming**: When designing concurrent programs, ownership
//! and borrowing rules necessitate careful planning to ensure that data is
//! accessed safely across threads. This often involves the use of thread-safe
//! wrappers and channels for communication.
//!
//! - **Explicit Resource Management**: Rust requires explicit management of
//! resources, which affects the design of program structures around RAII
//! (Resource Acquisition Is Initialization) patterns. This ensures that
//! resources are automatically released when they go out of scope.
//!
//! - **Function Signatures**: Function interfaces in Rust often need to express
//! not only the types of arguments but also their ownership and borrowing
//! semantics using lifetimes and borrowing rules.
//!
//! - **Patterns and Abstractions**: Common patterns in Rust, such as using
//! `Option` and `Result` types and the iterator pattern, influence the way
//! programs are structured. High-level abstractions might be used more
//! frequently to manage complexity.
//!
//! Overall, Rust's ownership model encourages developers to design programs with
//! a strong emphasis on safety, concurrency, and explicit resource management.
//! The result is often more verbose but also more robust and performant code
//! compared to languages with automatic garbage collection or manual memory
//! management.
//!

fn main() {
println!("Ownership and Lifetimes");
}

0 comments on commit f011abc

Please sign in to comment.