Skip to content

Instantly share code, notes, and snippets.

@Jimmy-Z
Last active May 11, 2022 09:06
Show Gist options
  • Save Jimmy-Z/89d8cbd18cd2e8df75ab24b0e3c0a7de to your computer and use it in GitHub Desktop.
Save Jimmy-Z/89d8cbd18cd2e8df75ab24b0e3c0a7de to your computer and use it in GitHub Desktop.

Rust/Go notes

  • Rustacean and Gopher

    • quirky mascot, also Deno?
  • Rust has borrow, Go has GC

    • this is like comparing apples to oranges
    • I won't talk about it here
  • similar things

    • type inference
    • no auto/implicit conversion
    • no parentheses on condition expression (if, for)
      • but braces are required for the code block
    • implicit auto (de)reference when calling methods
      • calling methods defined on ref on base type, and vice versa
      • no C++ style ->
      • doesn't work for regular parameters
    • p for pointer in format string, Rust: "{:p}", Go: "%p"
      • they work for slice
  • variable initialization/default

    • Go:
      • every type has a zero value
        • 0 for numeric types, nil for pointer types, etc
        • user defined types have automatic zero values
      • variables defaults to zero
    • Rust:
      • there is a similar thing called std::default
        • user defined types could #[derive(Default)] or implement their own default behavior
      • it's not called when variable declaration doesn't include initialization
        • compiler error if used before initialization
  • slice

    • similar
      • fat pointer
      • functions/methods are mostly (and encouraged to be) implemented against slice instead of array/Vec
      • low/high bound could be omitted in slicing syntax: [..] or [:]
    • Rust: slice is borrow/reference by definition
    • Go slice = Rust Vec + Rust slice
      • you can create slice by make: s := make([]int, 10)
        • then the slice itself is the sole owner of that data
      • you can expand slices in place
    • Go slice can be nil
    • Go slice can only be compared(==) to (literal) nil
      • BTW, it's a bit different than "slice can only equal to nil", which is not true
        • when two slices are compared it's compiler error, not true or false
      • Rust does deep equal instead
      • go has reflect.DeepEqual
  • constants

    • Rust: must be type annotated, even when type inference obviously would have worked
       // error: missing type for `const` item
       const C = 0isize;
       // must be annotated
       const C: isize = 0;
    • Go: constants are bizarre
      • they can be implicitly converted
         // this is OK
         const n = 0
         var a int = n
         var b uint = n
         // not OK since implicit type conversions are not allowed
         a = b
      • really a different beast
         // arbitrary precision
         const n = 1 << 100
         var a float64 = n
         // won't compile since it overflows int64
         var b int64 = n
  • Go doesn't have tuple

    • but functions can return multiple values
      • which (I believe) in most other languages, is implemented as tuple
    • and multiple assignments, like a, b = x, y
      • again usually implemented as destructuring assignment from tuple
    • unit type is struct{}
      • rust uses (), a 0-tuple
  • Go claims to be statically-typed but it's practically dynamically-typed when interface is involved:

  • type inference quirks

    • Rust: array length won't work
      • this works:
         let a = [0, 1, 2];
      • this also works:
         let a: [_; 3] = [0, 1, 2];
      • this doesn't:
         let a: [usize; _] = [0, 1, 2];
      • while it's not the same thing, when you do similar thing in Go:
        • ordinary array literal
           a := [3]int{0, 1, 2}
        • if length is omitted, a becomes a slice instead
           a := []int{0, 1, 2}
        • there is no such use of _ in Go, this doesn't compile
           a := [_]int{0, 1, 2}
  • flow control

    • loop constructs
      • Rust has for in, while and loop
        • doesn't have C-style 3 clause for
      • Go has for only but multiple flavors
        • (C-style 3 clause) for ; ;
        • for k, v := range ... {} (like for in)
        • for condition {} (like while)
        • for {} (like loop)
    • Go switch case, Rust match
      • Go doesn't fallthrough on default
        • but a fallthrough keyword could be used when needed
      • Rust doesn't fallthrough
      • Rust match must be exhaustive
  • Rust function:

    • return at end can be omitted
      • return value can be specified with an expression without ;
  • Go function:

    • return can only be omitted when there is no return value
    • return value can be named
      • even then, return at end can not be omitted
  • Go didn't have generics

    • but some builtin types are like generics, example: array, slice, map, channel
      • Go have some magical builtin functions for them
        • like make, append, delete, len, cap
        • with type checks and all
        • definitely looks like generics
        • users can't implement things like that
    • an unpleasant example: sort.Slice from std
       package main
      
       import "sort"
      
       func main() {
       	sort.Slice(0, func(_, _ int) bool { return false })
       }
      • this thing compiles, horrifyingly
      • it panics if you run it though
      • notice that it can't take []any?
  • function overloading

    • Rust: no function overloading
      • method overloading is possible by implementing methods for different traits with the same name
        • like try_into and try_from
        • they can even have the same signature which isn't possible in C++ function overloading
    • Go: strictly speaking, no
      • but it's basically dynamic so
  • Go doesn't have operator overloading

  • Go doesn't have uint128

  • Rust trait bound vs Go interface satisfaction

    • trait must be implemented
      • also makes aforementioned method overloading possible
    • interfaces are just declarations
      • structural typing
        • difference from duck-typing?
    • function overloading and duck-typing are incompatible with each other?
  • external type

    • Rust: you can't implement external traits on external types
    • Go: you can only define methods on your own type
      • however type aliases are considered your own type
      • Go is structural typed so implementing external interface on your own type isn't really a problem
  • Go: interface can only have regular methods (as requirements)

    • not "static" methods, like new/from
      • Go usually have them as (package level) regular functions instead
      • this is bad news for generics
      • as a workaround from can be implemented on pointer type
        • only the pointer type satisfy the trait then
  • Go: can't define methods for interfaces

    • i.e. receiver can't be a interface type
    • very odd considering that defining functions which takes parameters of interface types is allowed
      • in Rust (and some other languages), receiver is just the first parameter
  • Go 1.18 introduced generics, as type parameters

    • https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md
    • https://tip.golang.org/doc/go1.18#generics
    • using interface as constraint, like Rust trait
      • introduced union constraint element in interfaces
        • they can only be used as constraints
    • generic types can have methods
    • however methods can't take type parameters
      • i.e. methods can't be more generic than it's receiving type
      • and this is by design
    • x/exp/slices and x/exp/maps
      • ironic?
    • known limitations as noted in the release note
      • most importantly structural typing on union constraints are not working
        • golang/go#52367
        • barely works for the example it used
        • basically renders union constraints useless
    • only two predefined constrains as of now: any, comparable
      • some more are defined in the x/exp/constraints package
        • they're defined as union constraint
          • you can't make your own type satisfy them
  • (Go) remember we can't define methods for interfaces?

    • but generic types can?
    • and generic types are constrained by interfaces?
    • wrapping interface in a generic type can be a workaround:
       type Foo interface{}
       type Wrapper[T Foo] [1]T
       // we can then define methods on that wrapper
       func (Wrapper[T]) foo(){}
      a lot like Rust newtype
    • then I thought:
       type Wrapper[U Foo] U
      but this doesn't work:
       : cannot use a type parameter as RHS in type declaration
      
      I feel attacked
  • visibility

    • Go use naming
    • Rust trait methods can't be private
      • there're some strange workarounds
  • closure, nested function, anonymous function

    • clarify terms, since they overlap a lot
      • nested
         fn foo() {
         	fn bar(x: isize) { x + 1 }
         }
      • anonymous function
         let foo = func(x int) { return x + 1 };
      • shorthand anonymous function
         let foo = |x| x + 1;
         let var = x => x + 1
      • closure
    • Rust
      • nested: yes, but not closure
      • anonymous: no
      • shorthand(closure): yes, and closure
    • Go
      • nested: no
      • anonymous: yes, and closure
      • shorthand: no
    • JS
      • nested: yes and closure
      • anonymous: yes and closure
      • shorthand(arrow): since ES6 and closure
    • both rust and go closure can't be recursive
      • probably due to the nature of anonymous function and order of evaluation
      • there're some workarounds
      • as comparison JS doesn't have this limitation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment