Tuesday, April 19, 2022

Go vs Rust - Types (Casting, Literals, Aliasing...)

Type Casting

Both Go and Rust does not support implicit type conversion because of its Strong Type System, which doesn’t allow it to do this.

Go

var badboys int = 1921
// explicit type conversion
var badboys2 float64 = float64(badboys)
var badboys3 int64 = int64(badboys)
var badboys4 uint = uint(badboys)

Rust

let decimal = 65.4321_f32;
// Explicit conversion
let integer = decimal as u8;
let character = integer as char;
// 1000 - 256 - 256 - 256 = 232
// Under the hood, the first 8 least significant bits (LSB) are kept,
// while the rest towards the most significant bit (MSB) get truncated.
println!("1000 as a u8 is : {}", 1000 as u8);
// -1 + 256 = 255
println!("  -1 as a u8 is : {}", (-1i8) as u8);

Literals

A literal of a value is a text representation of the value in code.

Go

0xF // the hex form (starts with a "0x" or "0X")
0XF
017 // the octal form (starts with a "0", "0o" or "0O")
0o17
0O17
0b1111 // the binary form (starts with a "0b" or "0B")
0B1111
15 // the decimal form (starts without a "0")

Rust

// Integers
123i32
123u32
123_444_474u32
0usize
// Hex, octal, binary
0xff_u8
0o70_i16
0b111_111_11001_0000_i32

Type Alias

Type aliasing refers to the technique of providing an alternate name for an existing type. 

Go

package main
import (
    "fmt"
    "reflect"
)
type foo struct{}
// set new name as bar
type bar = foo
funcmyFunc (i bar) {
    fmt.Println(reflect.TypeOf(i))
}
funcmain() {
    vari bar
    myFunc(i)
}

Rust

type Result<T> = std::result::Result<T, std::io::Error>;
type NanoSecond = u64;
type Inch = u64;
let nanoseconds: NanoSecond = 5 as u64_t;
let inches: Inch = 2 as u64_t;

Empty Type or Never Type

Nils in Go

Nil is a frequently used and important predeclared identifier in Go. It is the literal representation of zero values of many kinds of types. Many new Go programmers with experiences of some other popular languages may view nil as the counterpart of null (or NULL) in other languages. This is partly right, but there are many differences between nil in Go and null (or NULL) in other languages.

  • nil is a Predeclared Identifier in Go
  • nil can Represent Zero Values of Many Types
  • Predeclared nil Has Not a Default Type

package main
func main() {
// There must be sufficient information for
// compiler to deduce the type of a nil value.
_ = (*struct{})(nil)
_ = []int(nil)
_ = map[int]bool(nil)
_ = chan string(nil)
_ = (func())(nil)
_ = interface{}(nil)
// These lines are equivalent to the above lines.
var _ *struct{} = nil
var _ []int = nil
var _ map[int]bool = nil
var _ chan string = nil
var _ func() = nil
var _ interface{} = nil
// This following line doesn't compile.
var _ = nil
}

Rust

Rust has a special type named ! that’s known in type theory lingo as the empty type because it has no values.

fn bar() -> ! {
    // --snip--
}

This code is read as “the function bar returns never.” Functions that return never are called diverging functions. We can’t create values of the type ! so bar can never possibly return.

break, continue has a ! value and loop, panic! has the type !

Dynamically Sized Types and the Sized Trait

Due to Rust’s need to know certain details, such as how much space to allocate for a value of a particular type, there is a corner of its type system that can be confusing: the concept of dynamically sized types. Sometimes referred to as DSTs or unsized types, these types let us write code using values whose size we can know only at runtime.

To work with DSTs, 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. That is, a generic function definition like this:

fn generic<T>(t: T) {
    // --snip--
}

is actually treated as though we had written this:

fn generic<T: Sized>(t: T) {
    // --snip--
}

By default, generic functions will work only on types that have a known size at compile time. However, you can use the following special syntax to relax this restriction:

fn generic<T: ?Sized>(t: &T) {
    // --snip--
}

A trait bound on ?Sized means “T may or may not be Sized” and this notation overrides the default that generic types must have a known size at compile time. The ?Trait syntax with this meaning is only available for Sized, not any other traits.

Also note that we switched the type of the t parameter from T to &T. Because the type might not be Sized, we need to use it behind some kind of pointer. In this case, we’ve chosen a reference.


No comments:

Post a Comment