From f011abc64cbffcd0da3182da93fb923ee08d09a1 Mon Sep 17 00:00:00 2001 From: Jorge Luis Perez Date: Tue, 2 Apr 2024 18:00:58 +0000 Subject: [PATCH] Add "Ownership and Lifetimes" lab --- Cargo.lock | 4 + Cargo.toml | 2 +- .../Makefile | 9 +- .../ownership-lifetimes/Cargo.toml | 8 + .../ownership-lifetimes/Makefile | 39 +++ .../ownership-lifetimes/src/main.rs | 251 ++++++++++++++++++ 6 files changed, 309 insertions(+), 4 deletions(-) create mode 100644 week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Cargo.toml create mode 100644 week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Makefile create mode 100644 week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 7090402..be6c4b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -503,6 +503,10 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "ownership-lifetimes" +version = "0.1.0" + [[package]] name = "pagerank" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3f85e73..eefc0e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/week-2/Lesson 1 - Rust Safety and Security Features/Makefile b/week-2/Lesson 1 - Rust Safety and Security Features/Makefile index 49bfdfb..d1fde80 100644 --- a/week-2/Lesson 1 - Rust Safety and Security Features/Makefile +++ b/week-2/Lesson 1 - Rust Safety and Security Features/Makefile @@ -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 @@ -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 \ No newline at end of file + make -C "meet-safe-and-unsafe" clean test build + +ownership-lifetimes: ## Build ownership-lifetime project + make -C "ownership-lifetimes" clean test build \ No newline at end of file diff --git a/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Cargo.toml b/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Cargo.toml new file mode 100644 index 0000000..49dfb09 --- /dev/null +++ b/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Cargo.toml @@ -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] diff --git a/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Makefile b/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Makefile new file mode 100644 index 0000000..80a40fd --- /dev/null +++ b/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Makefile @@ -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)" diff --git a/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/src/main.rs b/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/src/main.rs new file mode 100644 index 0000000..a59f148 --- /dev/null +++ b/week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/src/main.rs @@ -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"); +}