Welcome To The Home Blockchain Of đŚRustaceans¶
The repo is a prerequisite of the Substrate-Framework and it would be nicer to practice too.
The repo is included Rust syntax, configuration and the goal of creating scratch codes like one is becuase of providing testbed environment of Blockchain.
The Next reason is to using some features of Rust-Lang that I had wanted to implement it after learning Rust.
Used json in the main runner of the project so that consume json transactions as a offchain blockchain.
As you follow materials you can see (future work) which means you can add these concepts to the project. I have some idea that you can affort on it to completing future works.
Smart Contracts, MultiSignature, RPC, Make A Better CLI, and something that you can implementing (Do not worry sice most of your works will merge to main branch. We will not create a framework or complete Blockchain because we just need to learning more and used it use-cases)
The difference between the current work and the prev works¶
I have tried to use fundamental concepts correctly, for example, all of us know any block have not any copy so because of it we are calling blockchain! Unlike many repositories on GitHub(testbed/scratched projects-non productive) that almost use Copy/Clone features of Rust-Lang. In the following, there are some features that cause a different project.
Currently Status: Under Refactoring
How To Contribute Easy¶
[Rust 2021 A Scratch Blockchain-1] Youtube-Rust 2021 A Scratch Blockchain-1
Rust 2021 A Scratch Blockchain-2 Youtube-Rust 2021 A Scratch Blockchain-2
Instructions for working with¶
{difficulty}: (optional-key-env) value default 0x00ffffffffffffffffffffffffffffff.It must be 32 byte.
{mode}: macro, string, file/ default mode is on the macrojson mode.
{macro, string} there is in project and you can not access or manipulate except by getting the project. serde_json support string and macro based on called library. {file} json file is external .json file that you can set it for command line
{file name} index directory of the project sample-bolocks.json
Example:
cargo build
DIFFICULTY=0x00000fffffffffffffffffffffffffff time cargo run file sample-bolocks.json
DIFFICULTY=0x00000fffffffffffffffffffffffffff time cargo run macrojson
DIFFICULTY=0x00000fffffffffffffffffffffffffff time cargo run stringjson
time cargo run
cargo watch -x run
Features¶
-
[â] Modular
-
[â] UnitTest(semi:future work)
-
[â] Customized Error Handling
-
[â] Json & String Data Deserialized
-
[â] Closure(Functional Programming)
-
[â] Cryptography-Hashing Alogrithm SHA-256
-
[â] Configuration Files(semi- devOps)
Used Concepts¶
- [â] Memoizationâ˘Lazyâ˘Evaluation¶
We can create a struct that will hold the closure and the resulting value of calling the closure.
The struct will execute the closure only if we need the resulting value, and it will cache the resulting value so the rest of our code doesnât have to be responsible for saving and reusing the result.
FnOnce consumes the variables it captures from its enclosing scope, known as the closureâs environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure canât take ownership of the same variables more than once, so it can be called only once.
FnMut can change the environment because it mutably borrows values.
Fn borrows values from the environment immutably. FnOnce: takes the whole value. FnMut: takes a mutable reference. Fn: takes a regular reference.
- [â] Coercion¶
Deref coercion is a convenience that Rust performs on arguments to functions and methods. Deref coercion works only on types that implement the Deref trait. Deref coercion converts such a type into a reference to another type. For example, deref coercion can convert &String to &str because String implements the Deref trait such that it returns &str.
The number of times that Deref::deref needs to be inserted is resolved at compile time, so there is no runtime penalty for taking advantage of deref coercion! Similar to how you use the Deref trait to override the * operator on immutable references, you can use the DerefMut trait to override the * operator on mutable references.the Drop trait is almost always used when implementing a smart pointer. For example, when a Box
is dropped it will deallocate the space on the heap that the box points to. Note that we didnât need to call the drop method explicitly.
- [â] DSTâ˘Orâ˘Unsizedtype¶
DSTs or unsized types': str(but not &str-So although a &T is a single value that stores the memory address of where the T is located, a &str is two values: the address of the str and its length. Rust has a particular trait called the Sized trait to determine whether or not a typeâs size is known at compile time. This trait is automatically implemented for everything whose size is known at compile time. In addition, Rust implicitly adds a bound on Sized to every generic function.
- [â] Operation¶
-> Methods are functions that are coupled to some object.
From a syntactic point of view, these are just functions that donât need to specify one of their arguments. Rather than calling open() and passing a File object in as an argument (read(f, buffer)), methods allow the main object to be implicit in the function call (f.read(buffer)) using the dot operator.
There are a number of theoretical differences between methods and functions, but a detailed discussion of those computer science topics is available in other books. Briefly, functions are regarded as pure, meaning their behavior is determined solely by their arguments. Methods are inherently impure, given that one of their arguments is effectively a side effect. These are muddy waters, though. Functions are perfectly capable of acting on side effects themselves. Moreover, methods are implemented with functions. And, to add an exception to an exception, objects sometimes implement static methods, which do not include implicit arguments. To define methods, Rust programmers use an impl block
- [â] Borrowchecker¶
The borrow checker checks that all access to data is legal, which allows Rust to prevent safety issues. Learning how this works will, at the very least, speed up your development time by helping you avoid run-ins with the compiler. More significantly though, learning to work with the borrow checker allows you to build larger software systems with confidence. It underpins the term fearless concurrency.
- [â] Borrowcheckerâ˘Lifetime¶
-> Lifetime=Timetolive=Subset of their scope
Make hypotheses about whether or not your experiments will pass the borrow checker before you compile reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, just like most of the time, types are inferred. We must annotate types when multiple types are possible. In a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways.
The main aim of lifetimes is to prevent dangling references, which cause a program to reference data other than the data itâs intended to reference. All references in Rust have a lifetime, even if they are not explicitly annotated. The compiler is capable of implicitly assigning lifetimes.
A valueâs lifetime is the period when accessing that value is valid behavior. A functionâs local variables live until the function returns, while global variables might live for the life of the program.
The notion of ownership is rather limited. An owner cleans up when its valuesâ lifetimes end.
Although every parameter has a lifetime, these checks are typically invisible as the compiler can infer most lifetimes by itself.
All values bound to a given lifetime must live as long as the last access to any value bound to that lifetime.
No lifetime annotations are required when calling a function.
Lifetime annotations donât change how long any of the references live. Just as functions can accept any type when the signature specifies a generic type parameter, functions can accept references with any lifetime by specifying a generic lifetime parameter.
Lifetime annotations describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes.
The lifetime annotations indicate that the references first and second must both live as long as that generic lifetime.
Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.
Although every parameter has a lifetime, these checks are typically invisible as the compiler can infer most lifetimes by itself
All values bound to a given lifetime must live as long as the last access to any value bound to that lifetime.
No lifetime annotations are required when calling a function.
Using two lifetime parameters (a and b) indicates that the lifetimes of i and j are decoupled.
fn add_with_lifetimes<'a, 'b>(i: &'a i32, j: &'b i32) -> i32 {}
Lifetime of that usage:
the LOC('existence time' or Line of code) between when a location is first used in a certain way, and when that usage stops.
Lifetime of that value:
the LOC (or actual time) between when a value is created, and when that value is dropped.
Might be useful when discussing open file descriptors, but also irrelevant here.
Ultimately, lifetime syntax is about connecting the lifetimes of various parameters and return values of functions. Once theyâre connected, Rust has enough information to allow memory-safe operations and disallow operations that would create dangling pointers or otherwise violate memory safety.
- [â] Dangle¶
The main aim of lifetimes is to prevent dangling references.which has an outer scope and an inner scope. In return section of a function primitive types need to define as (&'a or &'static)
- [â] Generic¶
You might be wondering whether there is a runtime cost when using generic type parameters. The good news is that using generic types won't make your run any slower than it would with concrete types.
Rust accomplishes this by performing monomorphization of the code using generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compile. Every programming language has tools for effectively handling the duplication of concepts.
In Rust, one such tool is generics. Generics are abstract stand-ins for concrete types or other properties. When weâre writing code, we can express the behavior of generics or how they relate to other generics without knowing what will be in their place when compiling and running the code.
Staticâ˘Dispatch(Passed)¶
-> Monomorphization
Dispatch is the mechanism to determine which specific version of code is actually run when it involves polymorphism. Two major forms of dispatch are static dispatch and dynamic dispatch. While Rust favors static dispatch, it also supports dynamic dispatch through a mechanism called âtrait objectsâ. When Rust compiles this code, it performs monomorphization.
The monomorphized version of the code looks like the following. The generic Option
is replaced with the specific definitions created by the compiler: versions of a polymorphic function (or any polymorphic entity) during compilation is called Monomorphization. Because Rust compiles generic code into code that specifies the type in each instance, we pay no runtime cost for using generics. When the code runs, it performs just as it would if we had duplicated each definition by hand. The process of monomorphization makes Rustâs generics extremely efficient at runtime. This is opposed to dynamic dispatch
- [â] Dynamicâ˘Dispatch¶
The code that results from monomorphization is doing static dispatch, which is when the compiler knows what method youâre calling at compile time. This is opposed to dynamic dispatch, which is when the compiler canât tell at compile time which method youâre calling. In dynamic dispatch cases, the compiler emits code that at runtime will figure out which method to call.
When we use trait objects, Rust must use dynamic dispatch. The compiler doesnât know all the types that might be used with the code that is using trait objects, so it doesnât know which method implemented on which type to call. Instead, at runtime, Rust uses the pointers inside the trait object to know which method to call. There is a runtime cost when this lookup happens that doesnât occur with static dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a methodâs code, which in turn prevents some optimizations.
- [-] Blanketâ˘Implementation¶
Any implementation where a type appears uncovered. impl
Foo for T, impl Bar for T, impl Bar > for T, and impl Bar for Vec are considered blanket impls. We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations and are extensively used in the Rust standard library. For example, the standard library implements the ToString trait on any type that implements the Display trait.
- [â] Bound(syntax)¶
Bounds are constraints on a type or trait. For example, if a bound is placed on the argument a function takes, types passed to that function must abide by that constraint.
- [â] Trait¶
We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior. Traits are similar to a feature often called interfaces in other languages, although with some differences.
What is a trait? A trait is a language feature that is analogous to an interface, protocol, or contract. If you have a background in object-oriented programming, consider a trait to be an abstract base class. If you have a background in functional programming, Rustâs traits are close to Haskellâs type classes these also support a form of inheritance thatâs common in most object oriented languages. For now, though, the thing to remember is that traits represent common behavior (Or reusable codes like println!)that types opt into via the syntax impl Trait for Type.
After the method signature, instead of providing an implementation within curly brackets, we use a semicolon.
This interface consists of associated items, which come in three varieties: functions, types, constants.
All traits define an implicit type parameter Self that refers to "the type that is implementing this interface".
Trait functions may omit the function body by replacing it with a semicolon. This indicates that the implementation must define the function. If the trait function defines a body, this definition acts as a default for any implementation which does not override it. Similarly, associated constants may omit the equals sign and expression to indicate implementations must define the constant value. Associated types must never define the type, the type may only be specified in an implementation.
- [â] Polymorphism¶
In a struct or enum, the data in the struct fields and the behavior in impl blocks are separated, whereas in other languages, the data and behavior combined into one concept is often labeled an object.However, trait objects are more like objects in other languages in the sense that they combine data and behavior.
- [â] Unrolling¶
It is an optimization that removes the overhead of the loop controlling code and instead generates repetitive code for each iteration of the loop.
- [â] Bindingâ˘Match¶
The compiler automatically references the Some, and since we're borrowing, name is bound as ref name automatically as well. If we were mutating:
//https://blog.rust-lang.org/2018/05/10/Rust-1.26.html#nicer-match-bindings
// `self` has type `&List`, and `*self` has type `List`, matching on a
// concrete type `T` is preferred over a match on a reference `&T`
// after Rust 2018 you can use self here and tail (with no ref) below as well,
// rust will infer &s and ref tail.
- [â] Dataraceâ˘Rustaceans¶
Note: The opposite of referencing by using & is dereferencing, which is accomplished with the dereference operator, *.
[-] Nan(philosophy)¶
Floating-point types include ânot a numberâ values (represented in Rust syntax as NAN values) to handle these cases.
NAN values poison other numbers. Almost all operations interacting with NAN return NAN. Another thing to be mindful of is that, by definition, NAN values are never equal. Programming language design is often thought of in terms of which features you include, but the features you exclude are important too. Rust doesnât have the null feature that many other languages have. Null is a value that means there is no value there. In languages with null, variables can always be in one of two states: null or not-null. In his 2009 presentation âNull References: The Billion Dollar Mistake,â Tony Hoare, the inventor of null, has this to say: I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldnât resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
To program defensively, make use of the is_nan() and is_finite() methods. Inducing a crash, rather than silently proceeding with a mathematical error, allows you to debug close to what has caused the problem. The following illustrates using the is_finite()
- [â] Duplication((literal)¶
Concept of copying the pointer, length, and capacity without copying the data probably sounds like making a shallow copy.
If we do want to deeply copy the heap data of the String, not just the stack data, we can use a common method called clone
- [â] Semantic(literal)¶
Primitive types are said to possess copy semantics, whereas all other types have move semantics. Adding more functionality (e.g., reference-counting semantics rather than move semantics) to types by wrapping these in other types typically reduces their run-time performance.
- [â] Zeroâ˘Costâ˘Abstractions(literal)¶
One of the ways this manifests is by not adding extra data around values within structs.
- [â] Coherence(literal)¶
-> Orphan = Traitâ˘Externalâ˘Implement
But we canât implement external traits on external types. For example, we canât implement the Display trait on Vec
within our aggregator crate, because Display and Vec are defined in the standard library and arenât local to our aggregator crate. This restriction is part of a property of programs called coherence, and more specifically the orphan rule, so named because the parent type is not present. This rule ensures that other peopleâs code canât break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldnât know which implementation to use.
Preserves contextual coherence of trace data from tasks/function/methods when logging.
For example new instance of a struct of course, as you probably already know, struct then you can just summerize your struct in a method.
- [â] Jargon(literal)¶
Functional programming jargon: âto cons x onto yâ informally means to construct a new container instance by putting the element x at the start of this new container, followed by the container y.Other, more complex recursive data types are useful in various situations, but by starting with the cons list, we can explore how boxes let us define a recursive data type without much distraction.
- [â] Refactor(literal)¶
One alternative to refactoring is to simply copy values. Doing this often is typically frowned upon, however, but it can be useful in a pinch. Primitive types like integers are a good example of that. Primitive types are cheap for a CPU to duplicateâso cheap, in fact, that Rust always copies these if it would otherwise worry about ownership being moved.
Types can opt into two modes of duplication: cloning and copying.
- [â] Patternâ˘Newtype¶
Using the Newtype Pattern to Implement External Traits on External Types 'thin wrapper around the type' : part of Vec
is noticed. We can make a Wrapper struct that holds an instance of Vec ; then we can implement Display on Wrapper and use the Vec value The downside of using this technique is that Wrapper is a new type, so it doesnât have the methods of the value itâs holding. We would have to implement all the methods of Vec directly on Wrapper such that the methods delegate to self.0, which would allow us to treat Wrapper exactly like a Vec . If we wanted the new type to have every method the inner type has, implementing the Deref trait (If we donât want the Wrapper type to have all the methods of the inner typeâfor example, to restrict the Wrapper typeâs behaviorâwe would have to implement just the methods we do want manually.)
- [â] Patternâ˘Designâ˘Interior(future work)¶
Interior mutability is a design pattern in Rust that allows you to mutate data even when there are immutable references to that data; normally, this action is disallowed by the borrowing rules. To mutate data, the pattern uses unsafe code inside a data structure to bend Rustâs usual rules that govern mutation and borrowing.
RefCell
type that follows the interior mutability pattern. Unlike Rc
, the RefCell type represents single ownership over the data it holds. So, what makes RefCell different from a type like Box ? Recall the borrowing rules... Similar to Rc
, RefCell is only for use in single-threaded scenarios and will give you a compile-time error if you try using it in a multithreaded context. At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.References must always be valid.
- [â] Typeâ˘Wraper(future work)¶
-> Wrapper type = Reference-Counted Value = Shared Ownership = Track valid references
Use wrapper types, which allow more flexibility than what is available by default. These, however, incur costs at runtime to ensure that Rustâs safety guarantees are maintained. Another way to phrase this is that Rust allows programmers to opt in to garbage collection.
- [â] Memâ˘Leak(future work)¶
-> Managing Memory Leak
Rustâs memory safety guarantees make it difficult, but not impossible, to accidentally create memory that is never cleaned up (known as a memory leak). Preventing memory leaks entirely is not one of Rustâs guarantees in the same way that disallowing data races at compile time is, meaning memory leaks are memory safe in Rust.
We can see that Rust allows memory leaks by using Rc
and RefCell : itâs possible to create references where items refer to each other in a cycle. This creates memory leaks because the reference count of each item in the cycle will never reach 0, and the values will never be dropped.
- [â] Memâ˘Doublefree(future work)¶
This is a problem: when s2 and s1 (s2 is copied s1 means 2different pointer and the same data) go out of scope, they will both try to free the same memory. This is known as a double free error and is one of the memory safety bugs we mentioned previously. Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities.
- [â] Memâ˘Deallocatingâ˘orâ˘RAII(future work)¶
Note: In C++, this pattern of deallocating resources at the end of an itemâs lifetime is sometimes called Resource Acquisition Is Initialization (RAII). The drop function in Rust will be familiar to you if youâve used RAII patterns.
- [â] Thread(future work)¶
Which parts of your code on different threads will run. This can lead to problems, such as:
Race conditions, where threads are accessing data or resources in an inconsistent order Deadlocks, where two threads are waiting for each other to finish using a resource the other thread has, preventing both threads from continuing Bugs that happen only in certain situations and are hard to reproduce and fix reliably.
- [â] Threadâ˘Strateges(future work)¶
-> Priority Performance
Stealing_Join: execute code in parallel when there are idle CPUs to handle it.
When join is called from outside the thread pool, the calling thread will block while the closures execute in the pool. When join is called within the pool, the calling thread still actively participates in the thread pool. It will begin by executing closure A (on the current thread). While it is doing that, it will advertise closure B as being available for other threads to execute. Once closure A has completed, the current thread will try to execute closure B; if however closure B has been stolen, then it will look for other work while waiting for the thief to fully execute closure B. (This is the typical work-stealing strategy). Send is require because we have jump from quick func(thread a) to part func(thread b) frequently.
Atomic: types provide primitive shared-memory communication between threads, and are the building blocks of other concurrent types. This module defines atomic versions of a select number of primitive types, including AtomicBool, AtomicIsize, AtomicUsize, AtomicI8, AtomicU16, etc. Atomic types present operations that, when used correctly, synchronize updates between threads. Each method takes an Ordering which represents the strength of the memory barrier for that operation. These orderings are the same as the C++20 atomic orderings. For more information see the nomicon.
Atomic variables are safe to share between threads (they implement Sync) but they do not themselves provide the mechanism for sharing and follow the threading model of Rust. The most common way to share an atomic variable is to put it into an Arc (an atomically-reference-counted shared pointer). Atomic types may be stored in static variables, initialized using the constant initializers like AtomicBool::new. Atomic statics are often used for lazy global initialization.
Spin_Loop_Yeild also known as busy loop and spin loop-If you want to sleep pause a thread for short amounts of time, or if your application is sensitive to timing, use a spin loop
- [â] Unsafeâ˘Externâ˘Mangling(future work)¶
> Mangling is when a compiler changes the name weâve given a function to a different name that contains more information for other parts of the compilation process to consume but is less human readable. Every programming language compiler mangles names slightly differently, so for a Rust function to be nameable by other languages, we must disable the Rust compilerâs name mangling.
- [â] Interiorâ˘Mutabilityâ˘Pattern¶
RefCell
- [â] OOPâ˘Stateâ˘DesignPattern(future work)¶
-> We can used it for smart contracts so we will need to implemented smart contracts
Using the state pattern means when the business requirements of the program change, we wonât need to change the code of the value holding the state or the code that uses the value. Weâll only need to update the code inside one of the state objects to change its rules or perhaps add more state objects.
e.g Post type. This type will use the state pattern and will hold a value that will be one of three state objects representing the various states a post can be inâdraft, waiting for review, or published. Changing from one state to another will be managed internally within the Post type. The states change in response to the methods called by our libraryâs users on the Post instance, but they donât have to manage the state changes directly. Also, users canât make a mistake with the states, like publishing a post before itâs reviewed.
- [â] Superpower(future work)¶
if the Rust compiler doesnât have enough information to be confident, it will reject the code. In these cases, you can use unsafe code to tell the compiler, âTrust me, I know what Iâm doing.â The downside is that you use it at your own risk: if you use unsafe code incorrectly, problems due to memory unsafety, such as null pointer dereferencing, can occur.
You can take five actions in unsafe Rust, called unsafe superpowers, that you canât in safe Rust. Those superpowers include the ability to:
Dereference a raw pointer
Call an unsafe function or method
Access or modify a mutable static variable
Implement an unsafe trait
Access fields of unions
Calling unsafe() would crash the program.
consider unsafe to be a warning sign rather than an indicator that youâre embarking on anything illegal. Unsafe means âthe same level of safety offered by C at all times.â
If you still had access to (via unsafe) they might still look like valid S, but any attempt to use them as valid S is undefined behavior. â https://cheats.rs/#unsafe-unsound-undefined-dark side of force Try to avoid "unsafe {}", often safer, faster solution without it. Exception: FFI. People are fallible, and mistakes will happen, but by requiring these five unsafe operations to be inside blocks annotated with unsafe youâll know that any errors related to memory safety must be within an unsafe block. Keep unsafe blocks small; youâll be thankful later when you investigate memory bugs. To isolate unsafe code as much as possible, itâs best to enclose unsafe code within a safe abstraction and provide a safe API, which weâll discuss later in the chapter when we examine unsafe functions and methods.
Parts of the standard library are implemented as safe abstractions over unsafe code that has been audited. Wrapping unsafe code in a safe abstraction prevents uses of unsafe from leaking out into all the places that you or your users might want to use the functionality implemented with unsafe code, because using a safe abstraction is safe.
Contributors¶
nom is the fruit of the work of many contributors over the years, many thanks for your help!
Welcome To The Home Blockchain Of đŚRustaceans¶
The repo is a prerequisite of the Substrate-Framework and it would be nicer to practice too.
The repo is included Rust syntax, configuration and the goal of creating scratch codes like one is becuase of providing testbed environment of Blockchain.
The Next reason is to using some features of Rust-Lang that I had wanted to implement it after learning Rust.
Used json in the main runner of the project so that consume json transactions as a offchain blockchain.
As you follow materials you can see (future work) which means you can add these concepts to the project. I have some idea that you can affort on it to completing future works.
Smart Contracts, MultiSignature, RPC, Make A Better CLI, and something that you can implementing (Do not worry sice most of your works will merge to main branch. We will not create a framework or complete Blockchain because we just need to learning more and used it use-cases)
The difference between the current work and the prev works¶
I have tried to use fundamental concepts correctly, for example, all of us know any block have not any copy so because of it we are calling blockchain! Unlike many repositories on GitHub(testbed/scratched projects-non productive) that almost use Copy/Clone attributes of Rust-Lang for creating block. In the following, there are some features that cause a different project.
Currently Status: Under refactoring with contributors
How To Contribute Easy¶
[Rust 2021 A Scratch Blockchain-1] Youtube-Rust 2021 A Scratch Blockchain-1
Rust 2021 A Scratch Blockchain-2 Youtube-Rust 2021 A Scratch Blockchain-2
Documents Crate¶
Features¶
-
[â] Modular
-
[â] Customized Error Handling
-
[â] Json & String Data Deserialized
-
[â] Functional Programming(Closure)
-
[â] Cryptography-Hashing Alogrithm SHA-256
-
[-] WebAssembly(Next future video)
-
[-] Unit & Integration Testing(structure-need more time in the future)
-
[-] Configuration Files(devOps-need more time in the future)
Instructions for working with¶
{difficulty}: (optional-key-env) value default 0x00ffffffffffffffffffffffffffffff.It must be 32 byte.
{mode}: macro, string, file/ default mode is on the macrojson mode.
{macro, string} there is in project and you can not access or manipulate except by getting the project. serde_json support string and macro based on called library. {file} json file is external .json file that you can set it for command line
{file name} index directory of the project sample-bolocks.json
Example:
cargo build
cargo run
RUST_LOG=INFO DIFFICULTY=0x00000fffffffffffffffffffffffffff time cargo run file sample-bolocks.json
DIFFICULTY=0x00000fffffffffffffffffffffffffff time cargo run macrojson
DIFFICULTY=0x00000fffffffffffffffffffffffffff time cargo run stringjson
time cargo run
cargo watch -x run
cargo test
Instructions for installing-bin¶
curl -LSfs https://github.com/armanriazi/armanriazi/blob/main/install-0.sh | sh -s -- --git armanriazi/rust-scratch-blockchain
How to consume the library¶
1.
2. Add new dependency 3. To update dependencies 4. Your main.rs of the programuse library_blockchain::*;
fn main() {
println!("Hello, world!");
library_blockchain::blockchain_executive::main();
}
Json File
{
"blocks":[{
"block1":[{
"transactions":[{
"transaction1":[{
"inputs":[{
}],
"outputs":[{
"to_addr": "Alice",
"value": "50"
},{
"to_addr": "Bob",
"value": "7"
}]
}]
}]
}] ,
"block2":[{
"transactions":[{
"transaction1":[{
"inputs":[{
}],
"outputs":[{
"to_addr": "Chris",
"value": "536"
}]
}],
"transaction2":[{
"inputs":[{
"to_addr": "Alice",
"value": "50"
},{
"to_addr": "Bob",
"value": "7"
}],
"outputs":[{
"to_addr": "Alice",
"value": "49"
},{
"to_addr": "Bob",
"value": "6"
}]
}]
}]
}],
"block3":[{
"transactions":[{
"transaction1":[{
"inputs":[{
}],
"outputs":[{
"to_addr": "Alice",
"value": "49"
},{
"to_addr": "Bob",
"value": "6"
}]
}]
}]
}]
}]
}
- Run of one of above commands.
RUST_LOG=info DIFFICULTY=0x0000ffffffffffffffffffffffffffff time cargo run file sample-three-block-noerror.json
Console Log
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/consume-rust-scratch-blockchain file sample-three-block-noerror.json`
Hello, world!
[2022-07-06T13:30:22Z INFO library_blockchain::blockchain_executive] ------------Welcome to env_logger------------
[2022-07-06T13:30:22Z INFO library_blockchain::blockchain_executive] Starting Up...
**************************************************************
Selected mode is file!
**BlOcKcHaIn SiGnAls:**
Block[0]: 17b9180cd95fef6c0e8ab81702ab9cfd835b4af373fdc7ee45d13cba69c40000 at: 1657114222903 with: 1 trx, nonce: 131263
[2022-07-06T13:30:24Z INFO library_blockchain::factory] Success updated With the block 1.
**BlOcKcHaIn SiGnAls:**
Block[1]: eeb73730eb18ef3efbadbe7b5f3cc1a07c0d97b6c97e7df8945e61089b280000 at: 1657114224538 with: 2 trx, nonce: 257740
[2022-07-06T13:30:30Z INFO library_blockchain::factory] Success updated With the block 2.
**BlOcKcHaIn SiGnAls:**
Block[2]: 3674f23d58002856c17816590f7e2ff195005ad477c67e704d61eead25710000 at: 1657114230226 with: 1 trx, nonce: 37407
[2022-07-06T13:30:30Z INFO library_blockchain::factory] Success updated With the block 3.
7.67user 0.16system 0:08.03elapsed 97%CPU (0avgtext+0avgdata 25300maxresident)k
2280inputs+0outputs (13major+6505minor)pagefaults 0swaps
WASM(future work)¶
For consume the library we used some WASM compiling strategy:
Used Concepts¶
- [â] Memoizationâ˘Lazyâ˘Evaluation¶
We can create a struct that will hold the closure and the resulting value of calling the closure.
The struct will execute the closure only if we need the resulting value, and it will cache the resulting value so the rest of our code doesnât have to be responsible for saving and reusing the result.
FnOnce consumes the variables it captures from its enclosing scope, known as the closureâs environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. The Once part of the name represents the fact that the closure canât take ownership of the same variables more than once, so it can be called only once.
FnMut can change the environment because it mutably borrows values.
Fn borrows values from the environment immutably. FnOnce: takes the whole value. FnMut: takes a mutable reference. Fn: takes a regular reference.
- [â] Coercion¶
Deref coercion is a convenience that Rust performs on arguments to functions and methods. Deref coercion works only on types that implement the Deref trait. Deref coercion converts such a type into a reference to another type. For example, deref coercion can convert &String to &str because String implements the Deref trait such that it returns &str.
The number of times that Deref::deref needs to be inserted is resolved at compile time, so there is no runtime penalty for taking advantage of deref coercion! Similar to how you use the Deref trait to override the * operator on immutable references, you can use the DerefMut trait to override the * operator on mutable references.the Drop trait is almost always used when implementing a smart pointer. For example, when a Box
is dropped it will deallocate the space on the heap that the box points to. Note that we didnât need to call the drop method explicitly.
- [â] DSTâ˘Orâ˘Unsizedtype¶
DSTs or unsized types': str(but not &str-So although a &T is a single value that stores the memory address of where the T is located, a &str is two values: the address of the str and its length. Rust has a particular trait called the Sized trait to determine whether or not a typeâs size is known at compile time. This trait is automatically implemented for everything whose size is known at compile time. In addition, Rust implicitly adds a bound on Sized to every generic function.
- [â] Operation¶
-> Methods are functions that are coupled to some object.
From a syntactic point of view, these are just functions that donât need to specify one of their arguments. Rather than calling open() and passing a File object in as an argument (read(f, buffer)), methods allow the main object to be implicit in the function call (f.read(buffer)) using the dot operator.
There are a number of theoretical differences between methods and functions, but a detailed discussion of those computer science topics is available in other books. Briefly, functions are regarded as pure, meaning their behavior is determined solely by their arguments. Methods are inherently impure, given that one of their arguments is effectively a side effect. These are muddy waters, though. Functions are perfectly capable of acting on side effects themselves. Moreover, methods are implemented with functions. And, to add an exception to an exception, objects sometimes implement static methods, which do not include implicit arguments. To define methods, Rust programmers use an impl block
- [â] Borrowchecker¶
The borrow checker checks that all access to data is legal, which allows Rust to prevent safety issues. Learning how this works will, at the very least, speed up your development time by helping you avoid run-ins with the compiler. More significantly though, learning to work with the borrow checker allows you to build larger software systems with confidence. It underpins the term fearless concurrency.
- [â] Borrowcheckerâ˘Lifetime¶
-> Lifetime=Timetolive=Subset of their scope
Make hypotheses about whether or not your experiments will pass the borrow checker before you compile reference in Rust has a lifetime, which is the scope for which that reference is valid. Most of the time, lifetimes are implicit and inferred, just like most of the time, types are inferred. We must annotate types when multiple types are possible. In a similar way, we must annotate lifetimes when the lifetimes of references could be related in a few different ways.
The main aim of lifetimes is to prevent dangling references, which cause a program to reference data other than the data itâs intended to reference. All references in Rust have a lifetime, even if they are not explicitly annotated. The compiler is capable of implicitly assigning lifetimes.
A valueâs lifetime is the period when accessing that value is valid behavior. A functionâs local variables live until the function returns, while global variables might live for the life of the program.
The notion of ownership is rather limited. An owner cleans up when its valuesâ lifetimes end.
Although every parameter has a lifetime, these checks are typically invisible as the compiler can infer most lifetimes by itself.
All values bound to a given lifetime must live as long as the last access to any value bound to that lifetime.
No lifetime annotations are required when calling a function.
Lifetime annotations donât change how long any of the references live. Just as functions can accept any type when the signature specifies a generic type parameter, functions can accept references with any lifetime by specifying a generic lifetime parameter.
Lifetime annotations describe the relationships of the lifetimes of multiple references to each other without affecting the lifetimes.
The lifetime annotations indicate that the references first and second must both live as long as that generic lifetime.
Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.
Although every parameter has a lifetime, these checks are typically invisible as the compiler can infer most lifetimes by itself
All values bound to a given lifetime must live as long as the last access to any value bound to that lifetime.
No lifetime annotations are required when calling a function.
Using two lifetime parameters (a and b) indicates that the lifetimes of i and j are decoupled.
fn add_with_lifetimes<'a, 'b>(i: &'a i32, j: &'b i32) -> i32 {}
Lifetime of that usage:
the LOC('existence time' or Line of code) between when a location is first used in a certain way, and when that usage stops.
Lifetime of that value:
the LOC (or actual time) between when a value is created, and when that value is dropped.
Might be useful when discussing open file descriptors, but also irrelevant here.
Ultimately, lifetime syntax is about connecting the lifetimes of various parameters and return values of functions. Once theyâre connected, Rust has enough information to allow memory-safe operations and disallow operations that would create dangling pointers or otherwise violate memory safety.
- [â] Dangle¶
The main aim of lifetimes is to prevent dangling references.which has an outer scope and an inner scope. In return section of a function primitive types need to define as (&'a or &'static)
- [â] Generic¶
You might be wondering whether there is a runtime cost when using generic type parameters. The good news is that using generic types won't make your run any slower than it would with concrete types.
Rust accomplishes this by performing monomorphization of the code using generics at compile time. Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compile. Every programming language has tools for effectively handling the duplication of concepts.
In Rust, one such tool is generics. Generics are abstract stand-ins for concrete types or other properties. When weâre writing code, we can express the behavior of generics or how they relate to other generics without knowing what will be in their place when compiling and running the code.
Staticâ˘Dispatch(Passed)¶
-> Monomorphization
Dispatch is the mechanism to determine which specific version of code is actually run when it involves polymorphism. Two major forms of dispatch are static dispatch and dynamic dispatch. While Rust favors static dispatch, it also supports dynamic dispatch through a mechanism called âtrait objectsâ. When Rust compiles this code, it performs monomorphization.
The monomorphized version of the code looks like the following. The generic Option
is replaced with the specific definitions created by the compiler: versions of a polymorphic function (or any polymorphic entity) during compilation is called Monomorphization. Because Rust compiles generic code into code that specifies the type in each instance, we pay no runtime cost for using generics. When the code runs, it performs just as it would if we had duplicated each definition by hand. The process of monomorphization makes Rustâs generics extremely efficient at runtime. This is opposed to dynamic dispatch
- [â] Dynamicâ˘Dispatch¶
The code that results from monomorphization is doing static dispatch, which is when the compiler knows what method youâre calling at compile time. This is opposed to dynamic dispatch, which is when the compiler canât tell at compile time which method youâre calling. In dynamic dispatch cases, the compiler emits code that at runtime will figure out which method to call.
When we use trait objects, Rust must use dynamic dispatch. The compiler doesnât know all the types that might be used with the code that is using trait objects, so it doesnât know which method implemented on which type to call. Instead, at runtime, Rust uses the pointers inside the trait object to know which method to call. There is a runtime cost when this lookup happens that doesnât occur with static dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a methodâs code, which in turn prevents some optimizations.
- [-] Blanketâ˘Implementation¶
Any implementation where a type appears uncovered. impl
Foo for T, impl Bar for T, impl Bar > for T, and impl Bar for Vec are considered blanket impls. We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations and are extensively used in the Rust standard library. For example, the standard library implements the ToString trait on any type that implements the Display trait.
- [â] Bound(syntax)¶
Bounds are constraints on a type or trait. For example, if a bound is placed on the argument a function takes, types passed to that function must abide by that constraint.
- [â] Trait¶
We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior. Traits are similar to a feature often called interfaces in other languages, although with some differences.
What is a trait? A trait is a language feature that is analogous to an interface, protocol, or contract. If you have a background in object-oriented programming, consider a trait to be an abstract base class. If you have a background in functional programming, Rustâs traits are close to Haskellâs type classes these also support a form of inheritance thatâs common in most object oriented languages. For now, though, the thing to remember is that traits represent common behavior (Or reusable codes like println!)that types opt into via the syntax impl Trait for Type.
After the method signature, instead of providing an implementation within curly brackets, we use a semicolon.
This interface consists of associated items, which come in three varieties: functions, types, constants.
All traits define an implicit type parameter Self that refers to "the type that is implementing this interface".
Trait functions may omit the function body by replacing it with a semicolon. This indicates that the implementation must define the function. If the trait function defines a body, this definition acts as a default for any implementation which does not override it. Similarly, associated constants may omit the equals sign and expression to indicate implementations must define the constant value. Associated types must never define the type, the type may only be specified in an implementation.
- [â] Polymorphism¶
In a struct or enum, the data in the struct fields and the behavior in impl blocks are separated, whereas in other languages, the data and behavior combined into one concept is often labeled an object.However, trait objects are more like objects in other languages in the sense that they combine data and behavior.
- [â] Unrolling¶
It is an optimization that removes the overhead of the loop controlling code and instead generates repetitive code for each iteration of the loop.
- [â] Bindingâ˘Match¶
The compiler automatically references the Some, and since we're borrowing, name is bound as ref name automatically as well. If we were mutating:
//https://blog.rust-lang.org/2018/05/10/Rust-1.26.html#nicer-match-bindings
// `self` has type `&List`, and `*self` has type `List`, matching on a
// concrete type `T` is preferred over a match on a reference `&T`
// after Rust 2018 you can use self here and tail (with no ref) below as well,
// rust will infer &s and ref tail.
- [â] Dataraceâ˘Rustaceans¶
Note: The opposite of referencing by using & is dereferencing, which is accomplished with the dereference operator, *.
[-] Nan(philosophy)¶
Floating-point types include ânot a numberâ values (represented in Rust syntax as NAN values) to handle these cases.
NAN values poison other numbers. Almost all operations interacting with NAN return NAN. Another thing to be mindful of is that, by definition, NAN values are never equal. Programming language design is often thought of in terms of which features you include, but the features you exclude are important too. Rust doesnât have the null feature that many other languages have. Null is a value that means there is no value there. In languages with null, variables can always be in one of two states: null or not-null. In his 2009 presentation âNull References: The Billion Dollar Mistake,â Tony Hoare, the inventor of null, has this to say: I call it my billion-dollar mistake. At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldnât resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
To program defensively, make use of the is_nan() and is_finite() methods. Inducing a crash, rather than silently proceeding with a mathematical error, allows you to debug close to what has caused the problem. The following illustrates using the is_finite()
- [â] Duplication((literal)¶
Concept of copying the pointer, length, and capacity without copying the data probably sounds like making a shallow copy.
If we do want to deeply copy the heap data of the String, not just the stack data, we can use a common method called clone
- [â] Semantic(literal)¶
Primitive types are said to possess copy semantics, whereas all other types have move semantics. Adding more functionality (e.g., reference-counting semantics rather than move semantics) to types by wrapping these in other types typically reduces their run-time performance.
- [â] Zeroâ˘Costâ˘Abstractions(literal)¶
One of the ways this manifests is by not adding extra data around values within structs.
- [â] Coherence(literal)¶
-> Orphan = Traitâ˘Externalâ˘Implement
But we canât implement external traits on external types. For example, we canât implement the Display trait on Vec
within our aggregator crate, because Display and Vec are defined in the standard library and arenât local to our aggregator crate. This restriction is part of a property of programs called coherence, and more specifically the orphan rule, so named because the parent type is not present. This rule ensures that other peopleâs code canât break your code and vice versa. Without the rule, two crates could implement the same trait for the same type, and Rust wouldnât know which implementation to use.
Preserves contextual coherence of trace data from tasks/function/methods when logging.
For example new instance of a struct of course, as you probably already know, struct then you can just summerize your struct in a method.
- [â] Jargon(literal)¶
Functional programming jargon: âto cons x onto yâ informally means to construct a new container instance by putting the element x at the start of this new container, followed by the container y.Other, more complex recursive data types are useful in various situations, but by starting with the cons list, we can explore how boxes let us define a recursive data type without much distraction.
- [â] Refactor(literal)¶
One alternative to refactoring is to simply copy values. Doing this often is typically frowned upon, however, but it can be useful in a pinch. Primitive types like integers are a good example of that. Primitive types are cheap for a CPU to duplicateâso cheap, in fact, that Rust always copies these if it would otherwise worry about ownership being moved.
Types can opt into two modes of duplication: cloning and copying.
- [â] Patternâ˘Newtype¶
Using the Newtype Pattern to Implement External Traits on External Types 'thin wrapper around the type' : part of Vec
is noticed. We can make a Wrapper struct that holds an instance of Vec ; then we can implement Display on Wrapper and use the Vec value The downside of using this technique is that Wrapper is a new type, so it doesnât have the methods of the value itâs holding. We would have to implement all the methods of Vec directly on Wrapper such that the methods delegate to self.0, which would allow us to treat Wrapper exactly like a Vec . If we wanted the new type to have every method the inner type has, implementing the Deref trait (If we donât want the Wrapper type to have all the methods of the inner typeâfor example, to restrict the Wrapper typeâs behaviorâwe would have to implement just the methods we do want manually.)
- [â] Patternâ˘Designâ˘Interior(future work)¶
Interior mutability is a design pattern in Rust that allows you to mutate data even when there are immutable references to that data; normally, this action is disallowed by the borrowing rules. To mutate data, the pattern uses unsafe code inside a data structure to bend Rustâs usual rules that govern mutation and borrowing.
RefCell
type that follows the interior mutability pattern. Unlike Rc
, the RefCell type represents single ownership over the data it holds. So, what makes RefCell different from a type like Box ? Recall the borrowing rules... Similar to Rc
, RefCell is only for use in single-threaded scenarios and will give you a compile-time error if you try using it in a multithreaded context. At any given time, you can have either (but not both of) one mutable reference or any number of immutable references.References must always be valid.
- [â] Typeâ˘Wraper(future work)¶
-> Wrapper type = Reference-Counted Value = Shared Ownership = Track valid references
Use wrapper types, which allow more flexibility than what is available by default. These, however, incur costs at runtime to ensure that Rustâs safety guarantees are maintained. Another way to phrase this is that Rust allows programmers to opt in to garbage collection.
- [â] Memâ˘Leak(future work)¶
-> Managing Memory Leak
Rustâs memory safety guarantees make it difficult, but not impossible, to accidentally create memory that is never cleaned up (known as a memory leak). Preventing memory leaks entirely is not one of Rustâs guarantees in the same way that disallowing data races at compile time is, meaning memory leaks are memory safe in Rust.
We can see that Rust allows memory leaks by using Rc
and RefCell : itâs possible to create references where items refer to each other in a cycle. This creates memory leaks because the reference count of each item in the cycle will never reach 0, and the values will never be dropped.
- [â] Memâ˘Doublefree(future work)¶
This is a problem: when s2 and s1 (s2 is copied s1 means 2different pointer and the same data) go out of scope, they will both try to free the same memory. This is known as a double free error and is one of the memory safety bugs we mentioned previously. Freeing memory twice can lead to memory corruption, which can potentially lead to security vulnerabilities.
- [â] Memâ˘Deallocatingâ˘orâ˘RAII(future work)¶
Note: In C++, this pattern of deallocating resources at the end of an itemâs lifetime is sometimes called Resource Acquisition Is Initialization (RAII). The drop function in Rust will be familiar to you if youâve used RAII patterns.
- [â] Thread(future work)¶
Which parts of your code on different threads will run. This can lead to problems, such as:
Race conditions, where threads are accessing data or resources in an inconsistent order Deadlocks, where two threads are waiting for each other to finish using a resource the other thread has, preventing both threads from continuing Bugs that happen only in certain situations and are hard to reproduce and fix reliably.
- [â] Threadâ˘Strateges(future work)¶
-> Priority Performance
Stealing_Join: execute code in parallel when there are idle CPUs to handle it.
When join is called from outside the thread pool, the calling thread will block while the closures execute in the pool. When join is called within the pool, the calling thread still actively participates in the thread pool. It will begin by executing closure A (on the current thread). While it is doing that, it will advertise closure B as being available for other threads to execute. Once closure A has completed, the current thread will try to execute closure B; if however closure B has been stolen, then it will look for other work while waiting for the thief to fully execute closure B. (This is the typical work-stealing strategy). Send is require because we have jump from quick func(thread a) to part func(thread b) frequently.
Atomic: types provide primitive shared-memory communication between threads, and are the building blocks of other concurrent types. This module defines atomic versions of a select number of primitive types, including AtomicBool, AtomicIsize, AtomicUsize, AtomicI8, AtomicU16, etc. Atomic types present operations that, when used correctly, synchronize updates between threads. Each method takes an Ordering which represents the strength of the memory barrier for that operation. These orderings are the same as the C++20 atomic orderings. For more information see the nomicon.
Atomic variables are safe to share between threads (they implement Sync) but they do not themselves provide the mechanism for sharing and follow the threading model of Rust. The most common way to share an atomic variable is to put it into an Arc (an atomically-reference-counted shared pointer). Atomic types may be stored in static variables, initialized using the constant initializers like AtomicBool::new. Atomic statics are often used for lazy global initialization.
Spin_Loop_Yeild also known as busy loop and spin loop-If you want to sleep pause a thread for short amounts of time, or if your application is sensitive to timing, use a spin loop
- [â] Unsafeâ˘Externâ˘Mangling(future work)¶
> Mangling is when a compiler changes the name weâve given a function to a different name that contains more information for other parts of the compilation process to consume but is less human readable. Every programming language compiler mangles names slightly differently, so for a Rust function to be nameable by other languages, we must disable the Rust compilerâs name mangling.
- [â] Interiorâ˘Mutabilityâ˘Pattern¶
RefCell
- [â] OOPâ˘Stateâ˘DesignPattern(future work)¶
-> We can used it for smart contracts so we will need to implemented smart contracts
Using the state pattern means when the business requirements of the program change, we wonât need to change the code of the value holding the state or the code that uses the value. Weâll only need to update the code inside one of the state objects to change its rules or perhaps add more state objects.
e.g Post type. This type will use the state pattern and will hold a value that will be one of three state objects representing the various states a post can be inâdraft, waiting for review, or published. Changing from one state to another will be managed internally within the Post type. The states change in response to the methods called by our libraryâs users on the Post instance, but they donât have to manage the state changes directly. Also, users canât make a mistake with the states, like publishing a post before itâs reviewed.
- [â] Superpower(future work)¶
if the Rust compiler doesnât have enough information to be confident, it will reject the code. In these cases, you can use unsafe code to tell the compiler, âTrust me, I know what Iâm doing.â The downside is that you use it at your own risk: if you use unsafe code incorrectly, problems due to memory unsafety, such as null pointer dereferencing, can occur.
You can take five actions in unsafe Rust, called unsafe superpowers, that you canât in safe Rust. Those superpowers include the ability to:
Dereference a raw pointer
Call an unsafe function or method
Access or modify a mutable static variable
Implement an unsafe trait
Access fields of unions
Calling unsafe() would crash the program.
consider unsafe to be a warning sign rather than an indicator that youâre embarking on anything illegal. Unsafe means âthe same level of safety offered by C at all times.â
If you still had access to (via unsafe) they might still look like valid S, but any attempt to use them as valid S is undefined behavior. â https://cheats.rs/#unsafe-unsound-undefined-dark side of force Try to avoid "unsafe {}", often safer, faster solution without it. Exception: FFI. People are fallible, and mistakes will happen, but by requiring these five unsafe operations to be inside blocks annotated with unsafe youâll know that any errors related to memory safety must be within an unsafe block. Keep unsafe blocks small; youâll be thankful later when you investigate memory bugs. To isolate unsafe code as much as possible, itâs best to enclose unsafe code within a safe abstraction and provide a safe API, which weâll discuss later in the chapter when we examine unsafe functions and methods.
Parts of the standard library are implemented as safe abstractions over unsafe code that has been audited. Wrapping unsafe code in a safe abstraction prevents uses of unsafe from leaking out into all the places that you or your users might want to use the functionality implemented with unsafe code, because using a safe abstraction is safe.
Contributors¶
nom is the fruit of the work of many contributors over the years, many thanks for your help!