Monday, April 25, 2022

Go vs Rust - Unsafe

Go

If you have a background of C language, you must know what pointer means and its usage. With pointer, you are free to operate any data at memory level which means you have great power, but this means that you have great responsibility as well. That's why it might be considered unsafe in lots of cases.

package main
import "fmt"
func double(x *int) {
    *x += *x
    x = nil
}
func main() {
    var a = 3
    double(&a)
    fmt.Println(a) // 6
    p := &a
    double(p)
    fmt.Println(a, p == nil) // 12 false
}

The major restrictions of pointer in GoLang are:

  • No mathematic operations can be performed on pointer
  • Cannot convert between different types of pointer
  • Different types of pointer cannot be compared with == or !=
  • Cannot assign one type of pointer to another type of pointer

GoLang Unsafe

There is also type unsafe pointer, it is unsafe.Pointer residing in unsafe package.

In unsafe package, there is a Pointer defined.

type ArbitraryType int
type Pointer *ArbitraryType

This is similar to void* in C. Also three additional functions are defined.

func Sizeof(x ArbitraryType) uintptr
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr

Sizeof returned the number of bytes x takes, it doesn't count the actual number of bytes its content takes. 

Offsetof returns the position where one member of a struct within the struct away from the beginning position of the struct

Alignof returns m which means the number of bytes which can be divided when align the memory in the struct.

unsafe package provide two important features:

  • Any pointer can be converted to unsafe.Pointer and vice versa
  • uintptr can be converted to unsafe.Pointer and vice versa

Get or update value of unexported property in struct

package main
import (
    "fmt"
    "unsafe"
)
type Programmer struct {
    name string
    language string
}
func main() {
    p := Programmer{"stefno", "go"}
    fmt.Println(p)
    name := (*string)(unsafe.Pointer(&p))
    *name = "qcrao"
    lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language)))
    *lang = "Golang"
    fmt.Println(p)
}


Rust

Unsafe Superpowers

To switch to unsafe Rust, use the unsafe keyword and then start a new block that holds the unsafe code. 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

Dereferencing a Raw Pointer

Different from references and smart pointers, raw pointers:

Are allowed to ignore the borrowing rules by having both immutable and mutable pointers or multiple mutable pointers to the same location
Aren’t guaranteed to point to valid memory
Are allowed to be null
Don’t implement any automatic cleanup

we can create raw pointers in safe code, but dereference raw pointers and read the data being pointed to that requires an unsafe block.

    let mut num = 5;
    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;
    unsafe {
        println!("r1 is: {}", *r1);
        println!("r2 is: {}", *r2);
    }

Calling an Unsafe Function or Method

    unsafe fn dangerous() {}
    unsafe {
        dangerous();
    }

Creating a Safe Abstraction over Unsafe Code

use std::slice;
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();
    assert!(mid <= len);
    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

Using extern Functions to Call External Code

Sometimes, your Rust code might need to interact with code written in another language. For this, Rust has a keyword, extern, that facilitates the creation and use of a Foreign Function Interface (FFI).

extern "C" {
    fn abs(input: i32) -> i32;
}
fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

We can also use extern to create an interface that allows other languages to call Rust functions. Instead of an extern block, we add the extern keyword and specify the application binary interface (ABI) to use just before the fn keyword. We also need to add a #[no_mangle] annotation to tell the Rust compiler not to mangle the name of this function. 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. 

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}

Accessing or Modifying a Mutable Static Variable

Static variables are similar to constants.  Static variables can only store references with the 'static lifetime, which means the Rust compiler can figure out the lifetime and we aren’t required to annotate it explicitly. Accessing an immutable static variable is safe.

Constants and immutable static variables might seem similar, but a subtle difference is that values in a static variable have a fixed address in memory. Using the value will always access the same data. Constants, on the other hand, are allowed to duplicate their data whenever they’re used.

Another difference between constants and static variables is that static variables can be mutable. Accessing and modifying mutable static variables is unsafe. 

static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}
fn main() {
    add_to_count(3);
    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

Implementing an Unsafe Trait

Another use case for unsafe is implementing an unsafe trait. A trait is unsafe when at least one of its methods has some invariant that the compiler can’t verify. We can declare that a trait is unsafe by adding the unsafe keyword before trait and marking the implementation of the trait as unsafe too.

unsafe trait Foo {
    // methods go here
}
unsafe impl Foo for i32 {
    // method implementations go here
}

Accessing Fields of a Union

The final action that works only with unsafe is accessing fields of a union. A union is similar to a struct, but only one declared field is used in a particular instance at one time. Unions are primarily used to interface with unions in C code. Accessing union fields is unsafe because Rust can’t guarantee the type of the data currently being stored in the union instance

Go vs Rust - Error Handling

 1 Go

Error handling in Go is a little different than other mainstream programming languages like Java, JavaScript, or Python. Go’s built-in errors don’t contain stack traces, nor do they support conventional try/catch methods to handle them. Instead, errors in Go are just values returned by functions, and they can be treated in much the same way as any other datatype - leading to a surprisingly lightweight and simple design.

The Error Type

The error type in Go is implemented as the following interface:

type error interface {
    Error() string
}

So basically, an error is anything that implements the Error() method, which returns an error message as a string. It’s that simple!

Constructing Errors

Errors can be constructed on the fly using Go’s built-in errors or fmt packages. For example, the following function uses the errors package to return a new error with a static error message:

package main
import "errors"
func DoSomething() error {
    return errors.New("something didn't work")
}

Similarly, the fmt package can be used to add dynamic data to the error, such as an int, string, or another error. For example:

package main
import "fmt"
func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, fmt.Errorf("can't divide '%d' by zero", a)
    }
    return a / b, nil
}

Note that fmt.Errorf will prove extremely useful when used to wrap another error with the %w format verb.

There are a few other important things to note in the example above.

  • Errors can be returned as nil, and in fact, it’s the default, or “zero”, value of on error in Go. This is important since checking if err != nil is the idiomatic way to determine if an error was encountered 
  • Errors are typically returned as the last argument in a function. Hence in our example above, we return an int and an error, in that order.
  • When we do return an error, the other arguments returned by the function are typically returned as their default “zero” value. A user of a function may expect that if a non-nil error is returned, then the other arguments returned are not relevant.
  • Lastly, error messages are usually written in lower-case and don’t end in punctuation. Exceptions can be made though, for example when including a proper noun, a function name that begins with a capital letter, etc.

Defining Expected Errors

Another important technique in Go is defining expected Errors so they can be checked for explicitly in other parts of the code. This becomes useful when you need to execute a different branch of code if a certain kind of error is encountered.

Defining Sentinel Errors

Building on the Divide function from earlier, we can improve the error signaling by pre-defining a “Sentinel” error. Calling functions can explicitly check for this error using errors.Is:

package main
import (
    "errors"
    "fmt"
)
var ErrDivideByZero = errors.New("divide by zero")
func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, ErrDivideByZero
    }
    return a / b, nil
}
func main() {
    a, b := 10, 0
    result, err := Divide(a, b)
    if err != nil {
        switch {
        case errors.Is(err, ErrDivideByZero):
            fmt.Println("divide by zero error")
        default:
            fmt.Printf("unexpected division error: %s\n", err)
        }
        return
    }
    fmt.Printf("%d / %d = %d\n", a, b, result)
}

Defining Custom Error Types

There can be times when you might want a little more functionality. Perhaps you want an error to carry additional data fields, or maybe the error’s message should populate itself with dynamic values when it’s printed.

Below is a slight rework of the previous example. Notice the new type DivisionError, which implements the Error interface. We can make use of errors.As to check and convert from a standard error to our more specific DivisionError.

package main
import (
    "errors"
    "fmt"
)
type DivisionError struct {
    IntA int
    IntB int
    Msg  string
}
func (e *DivisionError) Error() string { 
    return e.Msg
}
func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, &DivisionError{
            Msg: fmt.Sprintf("cannot divide '%d' by zero", a),
            IntA: a, IntB: b,
        }
    }
    return a / b, nil
}
func main() {
    a, b := 10, 0
    result, err := Divide(a, b)
    if err != nil {
        var divErr *DivisionError
        switch {
        case errors.As(err, &divErr):
            fmt.Printf("%d / %d is not mathematically valid: %s\n",
              divErr.IntA, divErr.IntB, divErr.Error())
        default:
            fmt.Printf("unexpected division error: %s\n", err)
        }
        return
    }
    fmt.Printf("%d / %d = %d\n", a, b, result)
}

Wrapping Errors

Often in real-world programs, there can be many more functions involved - from the function where the error is produced, to where it is eventually handled, and any number of additional functions in-between.

Uses fmt.Errorf with a %w verb to “wrap” errors as they “bubble up” through the other function calls.

func FindUser(username string) (*db.User, error) {
    u, err := db.Find(username)
    if err != nil {
        return nil, fmt.Errorf("FindUser: failed executing db query: %w", err)
    }
    return u, nil
}
func SetUserAge(u *db.User, age int) error {
    if err := db.SetAge(u, age); err != nil {
      return fmt.Errorf("SetUserAge: failed executing db update: %w", err)
    }
}
func FindAndSetUserAge(username string, age int) error {
  var user *User
  var err error
  user, err = FindUser(username)
  if err != nil {
      return fmt.Errorf("FindAndSetUserAge: %w", err)
  }
  if err = SetUserAge(user, age); err != nil {
      return fmt.Errorf("FindAndSetUserAge: %w", err)
  }
  return nil
}
func main() {
    if err := FindAndSetUserAge("bob@example.com", 21); err != nil {
        fmt.Println("failed finding or updating user: %s", err)
        return
    }
    fmt.Println("successfully updated user's age")
}

If used correctly, error wrapping can provide additional context about the lineage of an error, in ways similar to a traditional stack-trace.

Wrapping also preserves the original error, which means errors.Is and errors.As continue to work, regardless of how many times an error has been wrapped. We can also call errors.Unwrap to return the previous error in the chain.

In summary, here’s the gist of what was covered here:

  • Errors in Go are just lightweight pieces of data that implement the Error interface
  • Predefined errors will improve signaling, allowing us to check which error occurred
  • Wrap errors to add enough context to trace through function calls (similar to a stack trace)
2 Rust

Rust groups errors into two major categories: recoverable and unrecoverable errors.Rust doesn’t have exceptions. Instead, it has the type Result<T, E> for recoverable errors and the panic! macro that stops execution when the program encounters an unrecoverable error. 

Panic Macro and Unrecoverable Errors

panic! macro allows a program to terminate immediately and provide feedback to the caller of the program. It should be used when a program reaches an unrecoverable state.

fn main() {
   panic!("Hello");
   println!("End of main"); //unreachable statement
}

Unwrap

Expects self to be Ok/Some and returns the value contained within. If it is Err or None instead, it raises a panic with the contents of the error displayed.

fn main(){
   let result = is_even(10).unwrap();
   println!("result is {}",result);
}
fn is_even(no:i32)->Result<bool,String> {
   if no % 2 == 0 {
      return Ok(true);
   } else {
      return Err("NOT_AN_EVEN".to_string());
   }
}

or

use std::fs::File;
use std::io::ErrorKind;
fn main() {
    let f = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            })
        } else {
            panic!("Problem opening the file: {:?}", error);
        }
    });
}

Expect

Behaves like unwrap, except that it outputs a custom message before panicking in addition to the contents of the error.

use std::fs::File;
fn main(){
   let f = File::open("pqr.txt").expect("File not able to open");
   //file does not exist
   println!("end of main");
}

Propagating Errors
When a function’s implementation calls something that might fail, instead of handling the error within the function itself, you can return the error to the calling code so that it can decide what to do.

Result Enum and Recoverable Errors

Enum Result – <T,E> can be used to handle recoverable errors. It has two variants − OK and Err. T and E are generic type parameters. T represents the type of the value that will be returned in a success case within the OK variant, and E represents the type of the error that will be returned in a failure case within the Err variant.

enum Result<T,E> {
   OK(T),
   Err(E)
}

use std::fs::File;
fn main() {
   let f = File::open("main.jpg");   // main.jpg doesn't exist
   match f {
      Ok(f)=> {
         println!("file found {:?}",f);
      },
      Err(e)=> {
         println!("file not found \n{:?}",e);   //handled error
      }
   }
   println!("end of main");
}

Aliases for Result

type Result<T> = Result<T, std::io::Error>;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

A Shortcut for Propagating Errors: the ? Operator

use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

Defining an error type

Sometimes it simplifies the code to mask all of the different errors with a single type of error.

type Result<T> = std::result::Result<T, DoubleError>;
#[derive(Debug)]
enum DoubleError {
    EmptyVec,
    // We will defer to the parse error implementation for their error.
    // Supplying extra info requires adding more data to the type.
    Parse(ParseIntError),
}
impl fmt::Display for DoubleError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            DoubleError::EmptyVec =>
                write!(f, "please use a vector with at least one element"),
            // The wrapped error contains additional information and is available
            // via the source() method.
            DoubleError::Parse(..) =>
                write!(f, "the provided string could not be parsed as int"),
        }
    }
}
impl error::Error for DoubleError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match *self {
            DoubleError::EmptyVec => None,
            // The cause is the underlying implementation error type. Is implicitly
            // cast to the trait object `&error::Error`. This works because the
            // underlying type already implements the `Error` trait.
            DoubleError::Parse(ref e) => Some(e),
        }
    }
}






Sunday, April 24, 2022

Go vs Rust - Macros in Rust

 Macros

Fundamentally, macros are a way of writing code that writes other code, which is known as metaprogramming.

The term macro refers to a family of features in Rust: declarative macros with macro_rules! and three kinds of procedural macros:

  • Custom #[derive] macros that specify code added with the derive attribute used on structs and enums
  • Attribute-like macros that define custom attributes usable on any item
  • Function-like macros that look like function calls but operate on the tokens specified as their argument
The Difference Between Macros and Functions
  • macros are expanded before the compiler interprets the meaning of the code, so a macro can, for example, implement a trait on a given type. A function can’t, because it gets called at runtime and a trait needs to be implemented at compile time.
  • because you’re writing Rust code that writes Rust code. macro definitions are generally more difficult to read, understand, and maintain than function definitions.
  •  must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere.
The most widely used form of macros in Rust is declarative macros. These are also sometimes referred to as “macros by example,” “macro_rules! macros,” or just plain “macros.” At their core, declarative macros allow you to write something similar to a Rust match expression. 

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

The #[macro_export] annotation indicates that this macro should be made available whenever the crate in which the macro is defined is brought into scope. Without this annotation, the macro can’t be brought into scope.

We then start the macro definition with macro_rules! and the name of the macro we’re defining without the exclamation mark.

We have one arm with the pattern ( $( $x:expr ),* ), followed by => and the block of code associated with this pattern. If the pattern matches, the associated block of code will be emitted. 

First, a set of parentheses encompasses the whole pattern. A dollar sign ($) is next, followed by a set of parentheses that captures values that match the pattern within the parentheses for use in the replacement code. Within $() is $x:expr, which matches any Rust expression and gives the expression the name $x.

The comma following $() indicates that a literal comma separator character could optionally appear after the code that matches the code in $(). The * specifies that the pattern matches zero or more of whatever precedes the *.

When we call this macro with vec![1, 2, 3];, the $x pattern matches three times with the three expressions 1, 2, and 3.

{
    let mut temp_vec = Vec::new();
    temp_vec.push(1);
    temp_vec.push(2);
    temp_vec.push(3);
    temp_vec
}

Procedural Macros for Generating Code from Attributes

Procedural macros act more like functions (and are a type of procedure). Procedural macros accept some code as an input, operate on that code, and produce some code as an output.

When creating procedural macros, the definitions must reside in their own crate with a special crate type. We can have multiple kinds of procedural macros in the same crate.

We’ll need functionality from the syn and quote crates, as you’ll see in a moment, so we need to add them as dependencies. Add the following to the Cargo.toml file for hello_macro_derive:

Filename: hello_macro_derive/Cargo.toml

[lib]
proc-macro = true

[dependencies]
syn = "1.0"
quote = "1.0"

Filename: hello_macro_derive/src/lib.rs

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
    // Construct a representation of Rust code as a syntax tree
    // that we can manipulate
    let ast = syn::parse(input).unwrap();
    // Build the trait implementation
    impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
    let name = &ast.ident;
    let gen = quote! {
        impl HelloMacro for #name {
            fn hello_macro() {
                println!("Hello, Macro! My name is {}!", stringify!(#name));
            }
        }
    };
    gen.into()
}

Attribute-like Macros

Attribute-like macros are similar to custom derive macros, but instead of generating code for the derive attribute, they allow you to create new attributes. They’re also more flexible: derive only works for structs and enums; attributes can be applied to other items as well, such as functions.

#[route(GET, "/")]
fn index() {

The signature of the macro definition function would look like this:

#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {

Here, we have two parameters of type TokenStream. The first is for the contents of the attribute: the GET, "/" part. The second is the body of the item the attribute is attached to: in this case, fn index() {} and the rest of the function’s body.

Function-like Macros

Function-like macros define macros that look like function calls. Similarly to macro_rules! macros, they’re more flexible than functions; for example, they can take an unknown number of arguments. However, macro_rules! macros can be defined only using the match-like syntax. Function-like macros take a TokenStream parameter and their definition manipulates that TokenStream using Rust code as the other two types of procedural macros do.

An example of a function-like macro is an sql! macro that might be called like so:

let sql = sql!(SELECT * FROM posts WHERE id=1);

The sql! macro would be defined like this:

#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {




Saturday, April 23, 2022

Go vs Rust - Concurrency

 Go

Go has rich support for concurrency using goroutines and channels. 

Goroutines and Channels are a lightweight built-in feature for managing concurrency and communication between several functions executing at the same time. 

Goroutines

A goroutine is a function that is capable of running concurrently with other functions. To create a goroutine we use the keyword go followed by a function invocation:

package main
import "fmt"
func f(n int) {
  for i := 0; i < 10; i++ {
    fmt.Println(n, ":", i)
  }
}
func main() {
  go f(0)
  var input string
  fmt.Scanln(&input)
}

Channels

Channels provide a way for two goroutines to communicate with one another and synchronize their execution. Here is an example program using channels:

package main
import (
  "fmt"
  "time"
)
func pinger(c chan string) {
  for i := 0; ; i++ {
    c <- "ping"
  }
}
func printer(c chan string) {
  for {
    msg := <- c
    fmt.Println(msg)
    time.Sleep(time.Second * 1)
  }
}
func main() {
  var c chan string = make(chan string)
  go pinger(c)
  go printer(c)
  var input string
  fmt.Scanln(&input)
}

Channel Direction

We can specify a direction on a channel type thus restricting it to either sending or receiving. For example pinger's function signature can be changed to this:

func pinger(c chan<- string)

Now c can only be sent to. Attempting to receive from c will result in a compiler error. Similarly we can change printer to this:

func printer(c <-chan string)

A channel that doesn't have these restrictions is known as bi-directional. A bi-directional channel can be passed to a function that takes send-only or receive-only channels, but the reverse is not true.

Select

Go has a special statement called select which works like a switch but for channels:

The select statement is often used to implement a timeout. 

The default case happens immediately if none of the channels are ready.

select {
case msg1 := <- c1:
  fmt.Println("Message 1", msg1)
case msg2 := <- c2:
  fmt.Println("Message 2", msg2)
case <- time.After(time.Second):
  fmt.Println("timeout")
default:
  fmt.Println("nothing ready")
}

Buffered Channels

It's also possible to pass a second parameter to the make function when creating a channel:

c := make(chan int, 1)

This creates a buffered channel with a capacity of 1. Normally channels are synchronous; both sides of the channel will wait until the other side is ready. A buffered channel is asynchronous; sending or receiving a message will not wait unless the channel is already full.

Anonymous functions as goroutines

func main() {
fmt.Println("We are executing a goroutine")
arr := []int{2,3,4}
ch := make(chan int, len(arr))
go func(arr []int, ch chan int) {
for _, elem := range arr {
ch <- elem * 3
}
}(arr, ch)
for i := 0; i < len(arr); i++ {
fmt.Printf("Result: %v \n", <- ch)
}
}

Mutual exclusion

A problem that may arise when working with concurrency is when share the same resources, which shouldn’t be accessed at the same time by multiple goroutines.

In concurrency, the block of code that modifies shared resources is called the critical section.

package main
import (
"fmt"
"time"
)
var n = 1
var mu sync.Mutex
func timesThree() {
  mu.Lock()
defer mu.Unlock()
n *= 3
fmt.Println(n)
}
func main() {
fmt.Println("We are executing a goroutine")
for i := 0; i < 10; i++ {
go timesThree()
}
time.Sleep(time.Second)
}

Rust

Rust's memory safety features also apply to its concurrency story. Even concurrent Rust programs must be memory safe, having no data races. Rust's type system is up to the task, and gives you powerful ways to reason about concurrent code at compile time.

Send

The first trait we're going to talk about is Send. When a type T implements Send, it indicates that something of this type is able to have ownership transferred safely between threads.

This is important to enforce certain restrictions. For example, if we have a channel connecting two threads, we would want to be able to send some data down the channel and to the other thread. Therefore, we'd ensure that Send was implemented for that type.

Sync

The second of these traits is called Sync. When a type T implements Sync, it indicates that something of this type has no possibility of introducing memory unsafety when used from multiple threads concurrently through shared references. This implies that types which don't have interior mutability are inherently Sync, which includes simple primitive types (like u8) and aggregate types containing them.

For sharing references across threads, Rust provides a wrapper type called Arc<T>. Arc<T> implements Send and Sync if and only if T implements both Send and Sync. For example, an object of type Arc<RefCell<U>> cannot be transferred across threads because RefCell does not implement Sync, consequently Arc<RefCell<U>> would not implement Send.

These two traits allow you to use the type system to make strong guarantees about the properties of your code under concurrency.

Threads

The thread::spawn() method accepts a closure, which is executed in a new thread. It returns a handle to the thread, that can be used to wait for the child thread to finish and extract its result:

use std::thread;
fn main() {
    let handle = thread::spawn(|| {
        "Hello from a thread!"
    });
    println!("{}", handle.join().unwrap());
}

move closures

We can force our closure to take ownership of its environment with the move

use std::thread;
fn main() {
    let x = 1;
    thread::spawn(move || {
        println!("x is {}", x);
    });
}

Safe Shared Mutable State

use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
fn main() {
    let data = Arc::new(Mutex::new(vec![1, 2, 3]));
    for i in 0..3 {
        let data = data.clone();
        thread::spawn(move || {
            let mut data = data.lock().unwrap();
            data[0] += i;
        });
    }
    thread::sleep(Duration::from_millis(50));
}

Channels

use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc;
fn main() {
    let data = Arc::new(Mutex::new(0));
    // `tx` is the "transmitter" or "sender".
    // `rx` is the "receiver".
    let (tx, rx) = mpsc::channel();
    for _ in 0..10 {
        let (data, tx) = (data.clone(), tx.clone());
        thread::spawn(move || {
            let mut data = data.lock().unwrap();
            *data += 1;
            tx.send(()).unwrap();
        });
    }
    for _ in 0..10 {
        rx.recv().unwrap();
    }





Friday, April 22, 2022

Go vs Rust - Strings

String

Go

In Go language, strings are different from other languages like Java, C++, Python, etc. it is a sequence of variable-width characters where each and every character is represented by one or more bytes using UTF-8 Encoding. Or in other words, strings are the immutable chain of arbitrary bytes(including bytes with zero value) or string is a read-only slice of bytes and the bytes of the strings can be represented in the Unicode text using UTF-8 encoding.

Due to UTF-8 encoding Golang string can contain a text which is the mixture of any language present in the world, without any confusion and limitation of the page.

    // Creating and initializing a slice of byte
    myslice1 := []byte{0x47, 0x65, 0x65, 0x6b, 0x73}
  
    // Creating a string from the slice
    mystring1 := string(myslice1)
    mystr := "Welcome to GeeksforGeeks ??????"
    // Finding the length of the string
    // Using len() function
    length1 := len(mystr)
  
    // Using RuneCountInString() function
    length2 := utf8.RuneCountInString(mystr)
    res1 := strings.Trim(str1, "@$")

Rust

There are two types of strings in Rust: String and &str.

A String is stored as a vector of bytes (Vec<u8>), but guaranteed to always be a valid UTF-8 sequence. String is heap allocated, growable and not null terminated.

&str is a slice (&[u8]) that always points to a valid UTF-8 sequence, and can be used to view into a String, just like &[T] is a view into Vec<T>.

let pangram: &'static str = "the quick brown fox jumps over the lazy dog";

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // Note that s1 has been moved here and can no longer be used
print!("{} ", s3);

let mut s = String::from("lo");
s.push('l');
let s2 = "bar";
s.push_str(&s2);
print!("{} ", s);

String Slices as Parameters

A more experienced Rustacean would write the following linebecause it allows us to use the same function on both Strings and &strs:

fn first_word(s: &str) -> &str {

The concepts of ownership, borrowing, and slices are what ensure memory safety in Rust programs at compile time. The Rust language gives you control over your memory usage like other systems programming languages, but having the owner of data automatically clean up that data when the owner goes out of scope.

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }
    &s[..]
}
fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);
    s.clear(); // Error!
}

Bytes and Scalar Values and Grapheme Clusters

If we look at the Hindi word “नमस्ते” written in the Devanagari script, it is ultimately stored as a Vec of u8 values that looks like this:

[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]

That’s 18 bytes and is how computers ultimately store this data. If we look at them as Unicode scalar values, which are what Rust’s char type is, those bytes look like this:

['न', 'म', 'स', '्', 'त', 'े']
There are six char values here, but the fourth and sixth are not letters: they’re diacritics that don’t make sense on their own. Finally, if we look at them as grapheme clusters, we’d get what a person would call the four letters that make up the Hindi word:

["न", "म", "स्", "ते"]

If we need to perform operations on individual Unicode scalar values, the best way to do so is to use the chars method.

for c in "नमस्ते".chars() {
    println!("{}", c);
}

Indexing into a string is often a bad idea because it’s not clear what the return type of the string indexing operation should be: a byte value, a character, a grapheme cluster, or a string slice. Therefore, Rust asks you to be more specific if you really need to use indices to create string slices.

let hello = "Здравствуйте";
let s = &hello[0..4];

s will be a &str that contains the first four bytes of the string. Earlier, we mentioned that each of these characters was two bytes, which means s will be Зд.
What would happen if we used &hello[0..1]? The answer: Rust will panic at runtime. So should use ranges to create string slices with caution, because it can crash your program.

Escape character

Go

Escape character Description
\\ Backslash(\)
\000 Unicode character with the given 3-digit 8-bit octal code point
\’ Single quote (‘). It is only allowed inside character literals
\” Double quote (“). It is only allowed inside interpreted string literals
\a ASCII bell (BEL)
\b ASCII backspace (BS)
\f ASCII formfeed (FF)
\n ASCII linefeed (LF
\r ASCII carriage return (CR)
\t ASCII tab (TAB)
\uhhhh Unicode character with the given 4-digit 16-bit hex code point.
Unicode character with the given 8-digit 32-bit hex code point.
\v ASCII vertical tab (VT)
\xhh Unicode character with the given 2-digit 8-bit hex code point.

Rust 

All number literals allow _ as a visual separator: 1_234.0E+18f64

\x41 7-bit character code (exactly 2 digits, up to 0x7F)
\n Newline
\r Carriage return
\t Tab
\\ Backslash
\0 Null

\u{7FFF} 24-bit Unicode character code (up to 6 digits)

\' Single quote
\" Double quote


Go vs Rust - HashMap

 HashMap

The concept of HashMap is present in almost all programming languages like Java, C++, Python, it has key-value pairs and through key, we can get values of the map. Keys are unique no duplicates allowed in the key but the value can be duplicated.

Go

Maps are Go’s built-in associative data type (sometimes called hashes or dicts in other languages).

package main
import "fmt"
func main() {
    m := make(map[string]int)
    m["k1"] = 7
    m["k2"] = 13
    fmt.Println("map:", m)
    v1 := m["k1"]
    fmt.Println("v1: ", v1)
    fmt.Println("len:", len(m))
    delete(m, "k2")
    fmt.Println("map:", m)
    _, prs := m["k2"]
    fmt.Println("prs:", prs)
    n := map[string]int{"foo": 1, "bar": 2}
    fmt.Println("map:", n)
}

To initialize a map with some data, use a map literal:

commits := map[string]int{
    "rsc": 3711,
    "r":   2138,
    "gri": 1908,
    "adg": 912,
}

Concurrency

Maps are not safe for concurrent use. One common way to protect maps is with sync.RWMutex.

var counter = struct{
    sync.RWMutex
    m map[string]int
}{m: make(map[string]int)}

To read from the counter, take the read lock:

counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)

To write to the counter, take the write lock:

counter.Lock()
counter.m["some_key"]++
counter.Unlock()

Rust

let mut gfg=HashMap::new();
// inserting records 
gfg.insert("Data Structures","90");
gfg.insert("Algorithms","99");
gfg.entry(String::from("Blue")).or_insert("50");

for (key, val) in gfg.iter() {
    println!("{} {}", key, val);
}

if gfg.contains_key( & "FAANG") {
   println!("yes it contains the given key well done gfg");
}

println!("len of gfg HashMap={}",gfg.len());

gfg.remove(& "key");
let value= gfg.get(&"Algorithms");

// Update after check
for word in text.split_whitespace() {
    let count = map.entry(word).or_insert(0);
    *count += 1;
}


Go vs Rust - Slices

 Slices

Go

Slice is more powerful, flexible, convenient than an array, and is a lightweight data structure. Slice is a variable-length sequence which stores elements of a similar type. It is just like an array having an index value and length, but the size of the slice is resized they are not in fixed-size just like an array. Internally, slice and an array are connected with each other, a slice is a reference to an underlying array.

    // Creating a slice using the var keyword
    var my_slice_1 = []string{"Geeks", "for", "Geeks"}
    // Creating an array
    arr := [4]string{"Geeks", "for", "Geeks", "GFG"}
    // Creating slices from the given array
    var my_slice_2 = arr[1:2]
    // Creating slices from the given slice
    var my_slice_3 = my_slice_3[1:5]

Generally, make() function is used to create an empty slice. Here, empty slices are those slices that contain an empty array reference

    // Creating an array of size 7 and slice this array  till 4
    // and return the reference of the slice Using make function
    var my_slice_1 = make([]int, 4, 7)
    fmt.Printf("Slice 1 = %v, \nlength = %d, \ncapacity = %d\n",
                   my_slice_1, len(my_slice_1), cap(my_slice_1))
 
    // Creating another array of size 7 and return the reference of the slice
    // Using make function
    var my_slice_2 = make([]int, 7)
    fmt.Printf("Slice 2 = %v, \nlength = %d, \ncapacity = %d\n",
                   my_slice_2, len(my_slice_2), cap(my_slice_2))

Multi-Dimensional Slice

 // Creating multi-dimensional slice
    s1 := [][]int{{12, 34},
        {56, 47},
        {29, 40},
        {46, 78},
    }
   
 sort.Ints(sl)
sort.IntsAreSorted(sl)

Rust

Slice is a data type that does not have ownership. Slice references a contiguous memory allocation rather than the whole collection. Slice is used when you do not want the complete collection, or you want some part of it. 

fn main() {
    let gfg = "GFG is a great start to start coding and improve".to_string();   
    // for first character
    println!("first character ={}",&gfg[0..1] );   
      // for first three characters
    println!("first three character ={}",&gfg[..3] );
      // calculating length of String
    let length_of_string=gfg.len();   
      let x=5;  
    // start from first to last character
    println!("start from 0 to x ={}",&gfg[..x] ); 
      // start from x to last character
    println!("start from x to end ={}",&gfg[x..length_of_string]);   
      // start from first to last character
    println!("from start to last ={}",&gfg[..length_of_string])
}


lc2)




Go vs Rust - Array and Vector (Rust)

 Array

programming languages. In the program, sometimes we need to store a collection of data of the same type, like a list of student marks. Such type of collection is stored in a program using an Array. 

Go

An array is a fixed-length sequence that is used to store homogeneous elements in the memory. Due to their fixed length array are not much popular like Slice in Go language. In Go language, an array is of value type not of reference type. So when the array is assigned to a new variable, then the changes made in the new variable do not affect the original array.

// Creating an array of string type Using var keyword
var myarr[3]string
// Elements are assigned using index
myarr[0] = "GFG"
myarr[1] = "GeeksforGeeks"
myarr[2] = "Geek"

Using shorthand declaration

// Shorthand declaration of array
arr:= [4]string{"geek", "gfg", "Geeks1231", "GeeksforGeeks"}
fmt.Println("Length of the array 1 is:", len(arr))

// Creating an array whose size is determined 
// by the number of elements present in it
// Using ellipsis
myarray:= [...]string{"GFG", "gfg", "geeks",
                    "GeeksforGeeks", "GEEK"}

Multi-Dimensional Array

Multi-Dimensional arrays are the arrays of arrays of the same type. 

// Creating and initializing 2-dimensional array
// Using shorthand declaration
// Here the (,) Comma is necessary
arr:= [3][3]string{{"C#", "C", "Python"}, 
                   {"Java", "Scala", "Perl"},
                    {"C++", "Go", "HTML"},}

Rust

An Array in Rust programming is a fixed-sized collection of elements denoted by [T; N] where is T is the element type and N is the compile-time constant size of the array.

We can create an array in 2 different ways:

  • Simply a list with each element [a, b, c].
  • Repeat expression [N, X].  This will create an array with N copies of X.
let mut array: [i32; 5] = [0; 5];
array[1] = 1;
array[2] = 2;
array[3] = 3;
array[4] = 4;
assert_eq!([1, 2 , 3 ,4], &array[1..]);
let arr = [1,2,3,4,5];
println!("array size is :{}",arr.len());
for index in 0..5 {
    println!("index is: {} & value is : {}",index, arr[index]);
}

Multi Array

use std::ops::{Index, Range};
struct ArrayView<'arr> {
    data: &'arr [[usize; 50]; 50],
    offsets: [usize; 2],
    size: [usize; 2],
}

Vector in Rust

Vector is a module in Rust that provides the container space to store values. It is a contiguous resizable array type, with heap-allocated contents.It can be increase size dynamically during runtime.Its length defines the number of elements present in the vector. Its capacity defines the actual allocated space on the heap of this vector that is in the form of 2^n.

let v : Vec<i64> = Vec::new(); 

or

let v = vec!['G','E','E','K','S'];

Examples

    // here index is the non negative value which is smaller than the size of the vector
    let index: usize = 3;
 
    let ch: char = v[index];
    let ch: Option<&char> = v.get(index);
    //loop to iterate elements in vector
    for i in v 
    { 
        // iterating through i on the the vector
        print!("{} ",i); 
    } 
    v.push('A');
    v.push('B');
    v.push('C');



Go vs Rust - Generics

 Generics are a way of writing code that is independent of the specific types being used. Functions and types may now be written to use any of a set of types.

Generics adds three new big things to the language:

  • Type parameters for function and types.
  • Defining interface types as sets of types, including types that don’t have methods.
  • Type inference, which permits omitting type arguments in many cases when calling a function.
Go

package main
import ("fmt")
type Number interface {
    int8 | int64 | float64
}
func sumNumbers[N Number](s []N) N {
    var total N
    for _, num := range s {
        total += num
    }
    return total
}
func main() {
    ints := []int64{32, 64, 96, 128}    
    floats := []float64{32.0, 64.0, 96.1, 128.2}
    bytes := []int8{8, 16, 24, 32}  
    fmt.Println(sumNumbers(ints))
    fmt.Println(sumNumbers(floats))    
    fmt.Println(sumNumbers(bytes))
}

any is essentially an alias for interface{}

func Print[T any] (s []T) {
for _, v := range s {
fmt.Println(v)
}
}

Another way generics can be used is to employ them in type parameters, as a way to create generic type definitions.

type Number interface {
    int8 | int64 | float64
}
type CustomSlice[T Number] []T
func Print[N Number, T CustomSlice[N]] (s T) {
for _, v := range s {
fmt.Println(v)
}
}
func main(){
    sl := CustomSlice[int64]{32, 32, 32}
    Print(sl)
}

Rust

Rust accomplishes generic by performing monomorphization of the code that is 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 compiled.

struct Point<T> {
    x: T,
    y: T,
}
impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}
fn main() {
    let p = Point { x: 5, y: 10 };
    println!("p.x = {}", p.x());
}

Multiple bounds

impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}
// Expressing bounds with a `where` clause
impl <A, D> MyTrait<A, D> for YourType where
    A: TraitB + TraitC,
    D: TraitE + TraitF {}

New Type Idiom

To obtain the newtype's value as the base type, you may use the tuple or destructuring syntax like so:

struct Years(i64);
fn main() {
    let years = Years(42);
    let years_as_primitive_1: i64 = years.0; // Tuple
    let Years(years_as_primitive_2) = years; // Destructuring
}

Associated types

The use of "Associated types" improves the overall readability of code by moving inner types locally into a trait as output types. Syntax for the trait definition is as follows:

// `A` and `B` are defined in the trait via the `type` keyword.
// (Note: `type` in this context is different from `type` when used for
// aliases).
trait Contains {
    type A;
    type B;
    // Updated syntax to refer to these new types generically.
    fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}
// Without using associated types
fn difference<A, B, C>(container: &C) -> i32 where
    C: Contains<A, B> { ... }
// Using associated types
fn difference<C: Contains>(container: &C) -> i32 { ... }

Phantom type parameters

A phantom type parameter is one that doesn't show up at runtime, but is checked statically (and only) at compile time.

Data types can use extra generic type parameters to act as markers or to perform type checking at compile time. These extra parameters hold no storage values, and have no runtime behavior.

use std::marker::PhantomData;
// A phantom tuple struct which is generic over `A` with hidden parameter `B`.
#[derive(PartialEq)] // Allow equality test for this type.
struct PhantomTuple<A, B>(A,PhantomData<B>);
// A phantom type struct which is generic over `A` with hidden parameter `B`.
#[derive(PartialEq)] // Allow equality test for this type.
struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> }
// Note: Storage is allocated for generic type `A`, but not for `B`.
//       Therefore, `B` cannot be used in computations.
fn main() {
    // Here, `f32` and `f64` are the hidden parameters.
    // PhantomTuple type specified as `<char, f32>`.
    let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData);
    // PhantomTuple type specified as `<char, f64>`.
    let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData);
    // Type specified as `<char, f32>`.
    let _struct1: PhantomStruct<char, f32> = PhantomStruct {
        first: 'Q',
        phantom: PhantomData,
    };
    // Type specified as `<char, f64>`.
    let _struct2: PhantomStruct<char, f64> = PhantomStruct {
        first: 'Q',
        phantom: PhantomData,
    };    
}


Thursday, April 21, 2022

Go vs Rust - Functions & Methods & Closures

 Functions

A function is a group of statements that together perform a task. Both Go and Rust program has at least one function, which is main(). You can divide your code into separate functions. Logically, the division should be such that each function performs a specific task.

Go

Multiple results

package main
import "fmt"
func swap(x, y string) (string, string) {
   return y, x
}
func main() {
   a, b := swap("Mahesh", "Kumar")
   fmt.Println(a, b)
}

Named return values

func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}

Functions as Values

package main

import ("fmt" "math")

func main(){
   /* declare a function variable */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }

   /* use the function */
   fmt.Println(getSquareRoot(9))
}

Rust

fn add_one(x: i32) -> i32 {
    x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    f(arg) + f(arg)
}
fn main() {
    let answer = do_twice(add_one, 5);
    println!("The answer is: {}", answer);
}

Closures
Sometimes it is useful to wrap up a function and free variables for better clarity and reuse. The free variables that can be used come from the enclosing scope and are ‘closed over’ when used in the function. From this, we get the name ‘closures’ 

Function Closure in Go

Go programming language supports anonymous functions which can acts as function closures. Anonymous functions are used when we want to define a function inline without passing any name to it.

package main
import "fmt"
func getSequence() func() int {
   i:=0
   return func() int {
      i+=1
      return i  
   }
}
func main(){
   /* nextNumber is now a function with i as 0 */
   nextNumber := getSequence()  
   /* invoke nextNumber to increase i by 1 and return the same */
   fmt.Println(nextNumber())
   fmt.Println(nextNumber())
   
   /* create a new sequence and see the result, i is 0 again*/
   nextNumber1 := getSequence()  
   fmt.Println(nextNumber1())
}

Rust

Rust’s implementation of closures is a bit different than other languages. They are effectively syntax sugar for traits.

let plus_one = |x: i32| x + 1;

We have three separate traits to overload with:

Fn
FnMut
FnOnce

There are a few differences between these traits, but a big one is self: Fn takes &self, FnMut takes &mut self, and FnOnce takes self. This covers all three kinds of self via the usual method call syntax. But we’ve split them up into three traits, rather than having a single one. This gives us a large amount of control over what kind of closures we can take.

Closures and their environment

The environment for a closure can include bindings from its enclosing scope in addition to parameters and local bindings.

let num = 5;
let plus_num = |x: i32| x + num;

Move Closures

We can force our closure to take ownership of its environment with the move keyword

let num = 5;
let owns_num = move |x: i32| x + num;

Taking Closures as Arguments

Now that we know that closures are traits, we already know how to accept and return closures: the same as any other trait!

fn call_with_one<F>(some_closure: F) -> i32
    where F: Fn(i32) -> i32 {
    some_closure(1)
}
let answer = call_with_one(|x| x + 2);

In Rust, we can stack allocate our closure environment, and statically dispatch the call. This happens quite often with iterators and their adapters, which often take closures as arguments.

Of course, if we want dynamic dispatch, we can get that too. A trait object handles this case, as usual:

fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
    some_closure(1)
}
let answer = call_with_one(&|x| x + 2);

Now we take a trait object, a &Fn. And we have to make a reference to our closure when we pass it to call_with_one, so we use &||.

Higher-Ranked Trait Bounds

A closure that can borrow its argument only for its own invocation scope, not for the outer function's scope. In order to say that, we can use Higher-Ranked Trait Bounds with the for<...> syntax:

fn call_with_ref<F>(some_closure:F) -> i32
    where F: for<'a> Fn(&'a i32) -> i32 {

    let value = 0;
    some_closure(&value)
}

Returning Closures

Closures are represented by traits, which means you can’t return closures directly. In most cases where you might want to return a trait, you can instead use the concrete type that implements the trait as the return value of the function. But you can’t do that with closures because they don’t have a concrete type that is returnable. Rust doesn’t know how much space it will need to store the closure. We can use box to resolve this problem.

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

Function Pointers and Closures

A function pointer is kind of like a closure that has no environment. As such, you can pass a function pointer to any function expecting a closure argument.

fn call_with_one(some_closure: &Fn(i32) -> i32) -> i32 {
    some_closure(1)
}
fn add_one(i: i32) -> i32 {
    i + 1
}
let answer = call_with_one(&add_one);
assert_eq!(2, answer);

Methods

 Go

Go language support methods. Go methods are similar to Go function with one difference, i.e, the method contains a receiver argument in it. With the help of the receiver argument, the method can access the properties of the receiver.

package main
import (
   "fmt" 
   "math" 
)
/* define a circle */
type Circle struct {
   x,y,radius float64
}
/* define a method for circle */
func(circle Circle) area() float64 {
   return math.Pi * circle.radius * circle.radius
}
func main(){
   circle := Circle{x:0, y:0, radius:5}
   fmt.Printf("Circle area: %f", circle.area())
}

Rust 

Some functions are connected to a particular type. These come in two forms: associated functions, and methods. Associated functions are functions that are defined on a type generally, while methods are associated functions that are called on a particular instance of a type.

Associated Functions or Static Methods

The structure_name :: syntax is used to access a static method.

struct Point {
    x: f64,
    y: f64,
}
// Implementation block, all `Point` associated functions & methods go in here
impl Point {
    // This is an "associated function" because this function is associated with
    // a particular type, that is, Point.
    //
    // Associated functions don't need to be called with an instance.
    // These functions are generally used like constructors.
    fn origin() -> Point {
        Point { x: 0.0, y: 0.0 }
    }
    // Another associated function, taking two arguments:
    fn new(x: f64, y: f64) -> Point {
        Point { x: x, y: y }
    }
}

Methods or Instance Method

The first parameter of a method will be always self, which represents the calling instance of the structure. Methods operate on the data members of a structure.

struct Rectangle {
    p1: Point,
    p2: Point,
}
impl Rectangle {
    // This is a method. &self is sugar for `self: &Self`, where `Self` is the type of the
    // caller object. In this case `Self` = `Rectangle`
    fn area(&self) -> f64 {
        // `self` gives access to the struct fields via the dot operator
        let Point { x: x1, y: y1 } = self.p1;
        let Point { x: x2, y: y2 } = self.p2;
        // `abs` is a `f64` method that returns the absolute value of the
        // caller
        ((x1 - x2) * (y1 - y2)).abs()
    }
    fn perimeter(&self) -> f64 {
        let Point { x: x1, y: y1 } = self.p1;
        let Point { x: x2, y: y2 } = self.p2;
        2.0 * ((x1 - x2).abs() + (y1 - y2).abs())
    }
    // This method requires the caller object to be mutable
    // `&mut self` desugars to `self: &mut Self`
    fn translate(&mut self, x: f64, y: f64) {
        self.p1.x += x;
        self.p2.x += x;
        self.p1.y += y;
        self.p2.y += y;
    }
}