-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
309 additions
and
4 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains 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
This file contains 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
8 changes: 8 additions & 0 deletions
8
week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Cargo.toml
This file contains 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
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] |
39 changes: 39 additions & 0 deletions
39
week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/Makefile
This file contains 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
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)" |
251 changes: 251 additions & 0 deletions
251
week-2/Lesson 1 - Rust Safety and Security Features/ownership-lifetimes/src/main.rs
This file contains 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
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"); | ||
} |