Thursday, April 21, 2022

Go vs Rust - Interface vs Trait

 Interfaces in Go

An interface type is defined as a set of method signatures. A value of interface type can hold any value that implements those methods that is "Duck Typing". Implicit interfaces decouple the definition of an interface from its implementation.

package main
import (
    "fmt"
)
type Phone interface {
    call()
}
type IPhone struct {
}
func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}
func main() {
    var phone Phone
    phone = new(IPhone)
    phone.call()
}

Interface Inheritance

type Eater interface {
Eat()
}

type Runner interface {
Run()
}

type Animal interface {
Eater
Runner
}

Empty Interface

The empty interface type essentially describes no methods. It has no rules. And because of that, it follows that any and every object satisfies the empty interface.

package main

import "fmt"

func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}

func main() {
var i interface{}
describe(i)

i = 42
describe(i)

i = "hello"
describe(i)
}

Type Assertions

A type assertion provides access to an interface value's underlying concrete value. e.g. t := i.(T) or t, ok := i.(T)

if t, ok := i.(*S); ok {
    fmt.Println("s implements I", t)
}
or
switch t := i.(type) {
case *S:
    fmt.Println("i store *S", t)
case *R:
    fmt.Println("i store *R", t)
}

Trait in Rust

Traits are an abstract definition of shared behavior amongst different types. So, we can say that traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

pub struct Tweet {
    pub username: String,
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

let tweet = Tweet {
        username: String::from("horse_ebooks"),
};

println!("1 new tweet: {}", tweet.summarize());

Traits as Parameters

The impl Trait syntax is convenient and makes for more concise code in simple cases. The trait bound syntax can express more complexity in other cases.

If we wanted this function to allow item1 and item2 to have different types, using impl Trait would be appropriate (as long as both types implement Summary).

pub fn notify(item1: &impl Summary, item2: &impl Summary) {}

If we wanted to force both parameters to have the same type, that’s only possible to express using a trait bound, like this:

pub fn notify<T: Summary>(item1: &T, item2: &T) {}

Trait Combos

Specifying Multiple Trait Bounds with the + Syntax

pub fn notify(item: &(impl Summary + Display)) {}
or
pub fn notify<T: Summary + Display>(item: &T) {}

Clearer Trait Bounds with where Clauses

instead of writing this:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {}

we can use a where clause, like this:

fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{}

Supertraits

Rust has a way to specify that a trait is an extension of another trait, giving us something similar to subclassing in other languages.

trait Shape { fn area(&self) -> f64; }
trait Shape2 { fn area(&self) -> f64; }
trait Circle : Shape+Shape2 { fn area(&self) -> f64; }

Trait Constants

Traits can also have associated constants. This is less common than trait methods, but is not without its uses. Like with methods, constants may provide a default value.

trait ConstTrait {
    const GREETING: &'static str;
    const NUMBER: i32 = 42;
}

Using Trait Bounds to Conditionally Implement Methods

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

Associated Types

Associated types connect a type placeholder with a trait such that the trait method definitions can use these placeholder types in their signatures. The implementor of a trait will specify the concrete type to be used in this type’s place for the particular implementation. That way, we can define a trait that uses some types without needing to know exactly what those types are until the trait is implemented.

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}
impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
        // --snip--

This syntax seems comparable to that of generics. The difference is that when using generics, we must annotate the types in each implementation; because we can also implement Iterator<String> for Counter or any other type, we could have multiple implementations of Iterator for Counter. In other words, when a trait has a generic parameter, it can be implemented for a type multiple times, changing the concrete types of the generic type parameters each time. 

With associated types, we don’t need to annotate types because we can’t implement a trait on a type multiple times.

Default Generic Type Parameters and Operator Overloading

When we use generic type parameters, we can specify a default concrete type for the generic type. This eliminates the need for implementors of the trait to specify a concrete type if the default type works.

struct Point {
    x: i32,
    y: i32,
}
impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}
fn main() {
    assert_eq!(
        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
        Point { x: 3, y: 3 }
    );
}

The default generic type in this code is within the Add trait. Here is its definition

trait Add<Rhs=Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}

This code should look generally familiar: a trait with one method and an associated type. The new part is Rhs=Self: this syntax is called default type parameters. 

When we implemented Add for Point, we used the default for Rhs because we wanted to add two Point instances. Let’s look at an example of implementing the Add trait where we want to customize the Rhs type rather than using the default.

use std::ops::Add;
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
    type Output = Millimeters;
    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name

Instance methods

trait Pilot {
    fn fly(&self);
}
trait Wizard {
    fn fly(&self);
}
struct Human;
impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}
impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}
impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}
fn main() {
    let person = Human;
    Pilot::fly(&person);
    Wizard::fly(&person);
    person.fly();
}

Static Methods

trait Animal {
    fn baby_name() -> String;
}
struct Dog;
impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}
impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}
fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

Using the Newtype Pattern to Implement External Traits on External Types

 the orphan rule that states we’re allowed to implement a trait on a type as long as either the trait or the type are local to our crate. It’s possible to get around this restriction using the newtype pattern, which involves creating a new type in a tuple struct.

Newtype is a term that originates from the Haskell programming language. There is no runtime performance penalty for using this pattern, and the wrapper type is elided at compile time.

As an example, let’s say we want to implement Display on Vec<T>, which the orphan rule prevents us from doing directly because the Display trait and the Vec<T> type are defined outside our crate. We can make a Wrapper struct that holds an instance of Vec<T>; then we can implement Display on Wrapper and use the Vec<T> value.

use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}
fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}

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<T> directly on Wrapper such that the methods delegate to self.0, which would allow us to treat Wrapper exactly like a Vec<T>. If we wanted the new type to have every method the inner type has, implementing the Deref trait on the Wrapper to return the inner type would be a solution.








Tuesday, April 19, 2022

Go vs Rust - Tuple

 Tuple

A tuple is a collection of values of different types. Functions can use tuples to return multiple values, as tuples can hold any number of values.

Go 

There is no tuple type in the Go language. That means you can not use tuple type in your Go program but you may implement some of its functionality by following a few approaches. 

package main
import "fmt"
type Student struct {
name, age interface{}
}
func main() {
student1 := Student{"Alex", 21}
student2 := Student{"Alia", 18}
fmt.Println("student1 Info :", student1, "Student2 Info :", student2)
fmt.Println("Student1 Age :", student1.age)
fmt.Println("Student2 Name :", student2.name)
}

In a tuple, you can also return multiple values. Though Go language does not have any tuple type, you may apply it with the help of a function that returns multiple values.

package main

import "fmt"
func multipleValues() (string, int) {
return "Alex", 21
}
func main() {
name, age := multipleValues()
fmt.Println("Name :", name)
fmt.Println("Age :", age)
}

Rust

let long_tuple = (1u8, 2u16); 
// Values can be extracted from the tuple using tuple indexing
println!("long tuple first value: {}", long_tuple.0);
println!("long tuple second value: {}", long_tuple.1);
   
// Tuples can be tuple members
let tuple_of_tuples = ((1u8, 2u16, 2u32), (4u64, -1i8), -2i16);

//tuples can be destructured to create bindings
let tuple = (1, "hello", 4.5, true);
let (a, b, c, d) = tuple;
println!("{:?}, {:?}, {:?}, {:?}", a, b, c, d);



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.


Go vs Rust - Struct

1 A structure or struct is a user-defined type that allows to group/combine items of possibly different types into a single type. Any real-world entity which has some set of properties/fields can be represented as a struct. This concept is generally compared with the classes in object-oriented programming. It can be termed as a lightweight class that does not support inheritance but supports composition.

Go

type Address struct {
    name, city string
    Pincode int
}

// Declaring a variable of a `struct` type. All the struct fields are initialized with their zero value
var a Address 
fmt.Println(a)

// Declaring and initializing a struct using a struct literal
a1 := Address{"Akshay", "Dehradun", 3623572}

// Naming fields while initializing a struct
a2 := Address{Name: "Anikaa", city: "Ballia", Pincode: 277001}

// Pointers to a struct by &. Uninitialized fields are set to their corresponding zero-value
a3 := &Address{Name: "Delhi"}
fmt.Println("Address3: ", (*a3).Name)
fmt.Println("Address3: ", a3.Name)

Anonymous Structure

    // Creating and initializing the anonymous structure
    Element := struct {
        name      string
        branch    string
        language  string
        Particles int
    }{
        name:      "Pikachu",
        branch:    "ECE",
        language:  "C++",
        Particles: 498,
    }

Anonymous Fields 
It's like Tuple Struct in Rust. but not allowed the same data type in Go.

// Creating a structure with anonymous fields
type student struct {
    int
    string
    float64
}

    // Assigning values to the anonymous fields of the student structure
    value := student{123, "Bud", 8900.23}
  
    // Display the values of the fields
    fmt.Println("Enrollment number : ", value.int)
    fmt.Println("Student name : ", value.string)
    fmt.Println("Package price : ", value.float64)

Rust

#[derive(Debug)]
struct Person {
    name: String,
    age: u8,
}

// A unit struct
struct Unit;

// A tuple struct
struct Pair(i32, f32);

// A struct with two fields
struct Point {
    x: f32,
    y: f32,
}

// Structs can be reused as fields of another struct
#[allow(dead_code)]
struct Rectangle {
    top_left: Point,
    bottom_right: Point,
}

    // Create struct with field init shorthand
    let name = String::from("Peter");
    let age = 27;
    let peter = Person { name, age };

    // Print debug struct
    println!("{:?}", peter);

    // Instantiate a `Point`
    let point: Point = Point { x: 10.3, y: 0.4 };

    // Access the fields of the point
    println!("point coordinates: ({}, {})", point.x, point.y);

    // Make a new point by using struct update syntax to use the fields of our
    // other one
    let bottom_right = Point { x: 5.2, ..point };

    // `bottom_right.y` will be the same as `point.y` because we used that field
    // from `point`
    println!("second point: ({}, {})", bottom_right.x, bottom_right.y);

    // Destructure the point using a `let` binding
    let Point { x: left_edge, y: top_edge } = point;

    let _rectangle = Rectangle {
        // struct instantiation is an expression too
        top_left: Point { x: left_edge, y: top_edge },
        bottom_right: bottom_right,
    };

    // Instantiate a unit struct
    let _unit = Unit;

    // Instantiate a tuple struct
    let pair = Pair(1, 0.1);

    // Access the fields of a tuple struct
    println!("pair contains {:?} and {:?}", pair.0, pair.1);

    // Destructure a tuple struct
    let Pair(integer, decimal) = pair;

    println!("pair contains {:?} and {:?}", integer, decimal);

2 Inheritance
Prefer Composition to Inheritance. Both Go and Rust has no inheritance concept.

Go
Base structs can be embedded into a child struct and the methods of the base struct can be directly called on the child struct.

// Golang program to illustrate the concept of multiple inheritances
package main
  
import (
    "fmt"
)
  
// declaring first base struct 
type first struct{
    base_one string
}
  
// declaring second base struct
type second struct{
    base_two string
}
  
// function to return first struct variable
func (f first) printBase1() string{      
    return f.base_one
}
  
// function to return second struct variable
func (s second) printBase2() string{
    return s.base_two
}
  
// child struct which embeds both base structs
type child struct{
    // anonymous fields, struct embedding of multiple structs
    first
    second
}
  
// main function
func main() {
      
    // declaring an instance of child struct
    c1 := child{   
        // child struct can directly access base struct variables
        first{    
            base_one: "In base struct 1.",
        },
        second{
            base_two: "\nIn base struct 2.\n",
        },
    }
      
    // child struct can directly access base struct methods
    // printing base method using instance of child struct
    fmt.Println(c1.printBase1())
    fmt.Println(c1.printBase2())
}

Rust

struct MyStruct {
    name: String
}

struct Pair(MyStruct, f32);

fn main() {       
    let pair = Pair(MyStruct{name: String::from("bq")}, 0.1);
    println!("hello world {}", pair.0.name);
}

Go vs Rust - Enum (switch vs match)

 1 Enum

In Golang, Enum implemented quite differently than most other programming languages. In Golang, we use a predeclared identifier, ​iota, and the enums are not strictly typed.

type Direction int
const (
    North Direction = iota
    South
    East
    West
)
    var myDirection Direction
    myDirection = West
    if (myDirection == West) {
      fmt.Println("myDirection is West:", myDirection)
    }

Rust’s enums are most similar to algebraic data types in functional languages, such as F#, OCaml, and Haskell.

enum WebEvent {
    // An `enum` may either be `unit-like`,
    PageLoad,
    // like tuple structs,
    Paste(String),
    // or c-like structures.
    Click { x: i64, y: i64 },
}

2 Switch Statement in Go
  • Both optstatement and optexpression in the expression switch are optional statements.
  • If both optstatementand optexpression are present, then a semi-colon(;) is required in between them.
  • If the switch does not contain any expression, then the compiler assume that the expression is true.
  • The optional statement, i.e, optstatement contains simple statements like variable declarations, increment or assignment statements, or function calls, etc.
  • If a variable present in the optional statement, then the scope of the variable is limited to that switch statement.
  • In switch statement, the case and default statement does not contain any break statement. But you are allowed to use break and fallthrough statement if your program required.
  • The default statement is optional in switch statement.
  • If a case can contain multiple values and these values are separated by comma(,).
  • If a case does not contain any expression, then the compiler assume that te expression is true.
Switch statement with both optional statement, i.e, day:=4 and expression, i.e, day
    switch day:=4; day{
       case 1:
       fmt.Println("Monday")
       default: 
       fmt.Println("Invalid")
   }

Expression switch statement
var value int = 2
switch {
       case value == 1:
       fmt.Println("Hello")
       default: 
       fmt.Println("Invalid")
 }

or

    var value string = "five"
      
    // Switch statement without default statement
    // Multiple values in case statement
   switch value {
       case "one":
       fmt.Println("C#")
       case "four", "five", "six":
       fmt.Println("Java")
   }  

Type switch statement

var value interface{}
switch q:= value.(type) {
       case bool:
       fmt.Println("value is of boolean type")
       default:
       fmt.Printf("value is of type: %T", q)       
}

3 Rust provides pattern matching via the match keyword, which can be used like a C switch. The first matching arm is evaluated and all possible values must be covered.

let number = 13;
match number {
        // Match a single value
        1 => println!("One!"),
        // Match several values
        2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
        // Match an inclusive range
        13..=19 => println!("A teen"),
        // Handle the rest of cases
        _ => println!("Ain't special"),
}

or

fn inspect(event: WebEvent) {
    match event {
        WebEvent::PageLoad => println!("page loaded"),
        // Destructure `c` from inside the `enum`.
        WebEvent::KeyPress(c) => println!("pressed '{}'.", c),
        // Destructure `Click` into `x` and `y`.
        WebEvent::Click { x, y } => {
            println!("clicked at x={}, y={}.", x, y);
        },
    }
}







Monday, April 18, 2022

Go vs Rust - Control Flow

 1 If Expressions

In Rust, the if expression is different Go and other languages.

  • the boolean condition doesn't need to be surrounded by parentheses.
  • if-else conditionals are expressions, and, all branches must return the same type.
let number = if condition { 5 } else { 6 };
or
    let letter: Option<i32> = None;
    if let Some(i) = letter {
        println!("Matched {:?}!", i);
    } else {
        // Destructure failed. Change to the failure case.
        println!("Didn't match a number. Let's go with a letter!");
    }

2 Repetition with Loops

As simple for loop

Go

for i := 0; i < =4; i++{
      fmt.Printf("GeeksforGeeks\n")  
}

Rust
  for x in 0..=10 {
    println!("value of iterator is: {}", x);
  }

For loop as Infinite Loop

Go
for {
      fmt.Printf("GeeksforGeeks\n")  
}

Rust
let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
};

for loop as while Loop

Go
for i < 3 {
    i += 2
}

Rust
while i < 3 {
    i += 2;
}
or
    // Make `optional` of type `Option<i32>`
    let mut optional = Some(0);

    // This reads: "while `let` destructures `optional` into
    // `Some(i)`, evaluate the block (`{}`). Else `break`.
    while let Some(i) = optional {
        if i > 9 {
            println!("Greater than 9, quit!");
            optional = None;
        } else {
            println!("`i` is `{:?}`. Try again.", i);
            optional = Some(i + 1);
        }
    }

Simple range in for loop

Go
     // Here rvariable is a array
    rvariable:= []string{"GFG", "Geeks", "GeeksforGeeks"} 
      
    // i and j stores the value of rvariable
    // i store index number of individual string and
    // j store individual string of the given array
    for i, j:= range rvariable {
       fmt.Println(i, j) 
    }

Rust
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {}", element);
    }
or
    for number in (1..4).rev() {
        println!("{}!", number);
    }

Using for loop for strings

Go
    for i, j:= range "XabCd" {
       fmt.Printf("The index number of %U is %d\n", j, i) 
    }

Rust
for (i, c) in my_str.chars().enumerate() {
    // do something with character `c` and index `i`
}

For Maps

Go
    mmap := map[int]string{
        22:"Geeks",
        33:"GFG",
        44:"GeeksforGeeks",
    }
    for key, value:= range mmap {
       fmt.Println(key, value) 
    }

Rust
for (k, x) in &mymap {
    println!("Key={key}, Value={val}", key=k, val=x);
}

For Channel

    chnl := make(chan int)
    go func(){
        chnl <- 100
        chnl <- 1000
       chnl <- 10000
       chnl <- 100000
       close(chnl)
    }()
    for i:= range chnl {
       fmt.Println(i) 
    }

Go
  • Go contains only a single loop that is for-loop.
  • Parentheses are not used around the three statements of a for loop.
  • The curly braces are mandatory in for loop.
  • The opening brace should be in the same line in which post statement exists.
  • If the array, string, slice, or map is empty, then for loop does not give an error and continue its flow. Or in other words, if the array, string, slice, or map is nil then the number of iterations of the for loop is zero.