Top 7 Modern Programming Languages to Learn in 2020

Top 7 Modern Programming Languages to Learn in 2020

Top 7 Modern Programming Languages to Learn in 2020. How Rust, Go, Kotlin, TypeScript, Swift, Dart, Julia can boost your career and improve your software development skills

If we think about modern human civilization as a car, then the software development industry is like the engine of the car and programming languages are like fuel to the engine. Which programming language should you learn?

Learning a new programming language is a big investment in time, energy, and brainpower.

Usually, choose a programming language that gives you a boost in your career. Also, learn a language whose popularity is ascending. This means that you should learn established and hugely popular programming languages.

I have huge respect in mainstream programming languages. But here I will give you a list of modern programming languages that can improve your productivity, boost your career, and make you a better developer. Also, I will cover a wide variety of domains: system programming, app development, web development, scientific computing.

The term “Modern programming language” is ambiguous. Many consider languages like Python, JavaScript as modern programming languages. At the same time, they consider Java as an Old programming language. In reality, all of them appeared around the same time: 1995.

Most of the mainstream programming languages were developed in the last century, mainly in the 1970s (e.g. C), 1980s (e.g. C++), 1990s (e.g. Java, Python, JavaScript). These languages were not designed to take advantage of modern-day software development ecosystems: Multi-Core CPU, GPU, fast networking, mobile devices, Container, and Cloud. Although many of them have retrofit features like Concurrency in their language and adapted themselves, they also offer Backward compatibility and could not throw away the old, obsolete features.

Python did a Good job (or Bad depending on the context) by making a clear cut between Python 2 and Python 3. Those languages often offer 10 ways to do the same things and do not care about developer ergonomics. According to the StackOverflow developer survey, most of the mainstream old programming languages share top spots in the “most dreaded language” category:

I would put a clear boundary between the old and new programming languages is on June 29, 2007, when the first iPhone was released. After that, the landscape has transformed. In this list, I will consider post-2007 programming languages.

First, modern programming languages are developed to take the full advantages of modern computer hardware (Multi-Core CPU, GPU, TPU), mobile devices, large-set of data, fast networking, Container, and Cloud. Also, most of the modern programming languages offer much higher developer Ergonomics as given below:

  • Concise and terse code (less boilerplate coding)
  • Built-in support for concurrency
  • Null pointer safety
  • Type Inference
  • The much simpler feature set
  • Lower cognitive load
  • Blending the best features of all programming paradigms

Second, many programming languages of the list are disruptive and will change the software industry forever. Some of them are already mainstream programming languages, while others are poised to make the breakthrough. It is wise to learn those languages at least as a second programming language.

Rust

The System programming language landscape is dominated by near-Metal languages like C, C++. Although they give full control over programs and hardware, they lack memory safety. Even if they support concurrency, it is challenging to write Concurrent programs using C/C++ as there is no Concurrency safety. The other popular programming languages are interpreted languages like Java, Python, Haskell. They offer safety but need a bulky runtime or Virtual Machine. Because of their large runtime, languages like Java are not suitable for System programming.

There were many attempts to combine the power of C/C++ and the safety of Haskell, Java. It looks like Rust is the first production-grade programming language that did the trick.

Graydon Hoare first developed Rust as a side project. He was inspired by the research programming language Cyclone. Rust is open source and Mozilla is leading the language development along with many other companies and communities. Rust is first released in 2015 and has soon caught the eye of the community.

Key Features:

  • Offers Memory Safety and Concurrency safety with the concept of Ownership and Borrowing.
  • Compile-time guarantee of memory safety and concurrency safety i.e. if a program code compiles, then it is both memory safe and data-race free. This is the most appealing feature of Rust.
  • It also offers the expressiveness of ML, Haskell. With Immutable Data Structures and functional programming features, Rust offers functional Concurrency and Data Concurrency.
  • Rust is Blazingly fast. Idiomatic Rust gives better performance than Idiomatic C++ as per Benchmark Game.
  • With no Runtime, Rust offers full control of modern Hardware (TPU, GPU, Multi-Core CPU).
  • Rust has LLVM support. As a result, Rust offers first-class interoperability with WebAssembly and allows the Blazingly fast Web Code.

Popularity:

Since its debut in 2015, Rust is well accepted by the Developers and voted as the Most beloved language for four consecutive years (2016, 2017, 2018, 2019) in StackOverflow developers survey:

According to GitHub Octoverse, Rust is the second-fastest-growing language just behind Dart:

Also, programming language Popularity website PyPl has ranked Rust in 18th position with an upward trend:

Comparing the feature set it offers, it is no wonder that giant Tech companies like Microsoft, Amazon, Google finally announced their investment on Rust as a long term System programming language.

In the last 5 years, Rust has been getting increasing traction every year, as shown by Google trends:

Main Use Cases:

  • System Programming
  • Serverless Computing
  • Business Applications

Main competitor Languages:

  • C
  • C++
  • Go
  • Swift
Go

Google is one of the biggest Web Scale companies. At the beginning of this century, Google has faced two scaling problems: Development Scaling and Application Scaling. Development scaling means that they could not add more features by throwing more developers. Application scaling means that they could not easily develop an application that can scale to the “Google” scale machine cluster. Around 2007, Google started to create a new “pragmatic” programming language that can solve these two scaling problems. In Rob Pike (UTF-8) and Ken Thompson (UNIX OS), they had two most talented Software Engineer in the world to create a new language.

In 2012, Google has released the first official version of the Go programming language. Go is a system programming language but different from Rust. It also has a Runtime and Garbage collector (a few Megabytes). But unlike Java or Python, this runtime is packed with the generated code. In the end, Go generates a single native binary code that can run in a machine without additional dependency or Runtime.

Key Features:

  • Go has first-class support of Concurrency. It does not offer the ‘Shared Memory’ concurrency via Thread and Lock as it is much more difficult to program. Instead, it offers a CSP based message-passing concurrency (based on Tony Hoare paper). Go uses “Goroutine” (lightweight Green thread) and “Channel” for message passing.
  • The most killer feature of Go is its simplicity. It is the most simple system programming language. A new Software Developer can write productive code in a matter of days like Python. Some of the biggest Cloud Native projects (Kubernetes, Docker) is written in Go.
  • Go also has embedded Garbage Collector which means developers do not need to worry about Memory management like C/C++.
  • Google has invested heavily in Go. As a result, Go has massive Tooling support. For new Go developers, there is a large ecosystem of tools.
  • Usually, developers spent 20% of their time writing new code and 80% time they maintain existing code. Because of its simplicity, Go excels in the language maintenance field. Nowadays, Go used heavily in Business Applications.

Popularity:

Since Go first appears, the Software Development community has accepted it with arms. In 2009 (right after its debut) and 2018, Go has entered the Programming Language Hall of Fame list by the TIOBE index. It is no wonder that the success of Go has paved the way for a new generation of programming languages like Rust.

Go is already a mainstream programming language. Recently, Go team has announced the work on “Go 2” to only make the language more solid:

In almost all popular programming languages comparing websites, Go ranks high and has surpassed many existing languages. Here is the TIOBE index rating from December 2019 where Go ranks 15th:

According to the Stackoverflow survey, Go is one of the top 10 most loved programming languages:

Go is also one of the top 10 fastest growing languages according to GitHub Octoverse:

Google trends also show increasing traction for Go over the last five years:

Main Use Cases:

  • System Programming
  • Serverless Computing
  • Business Applications
  • Cloud-Native Development

Main competitor Languages:

  • C
  • C++
  • Rust
  • Python
  • Java
Kotlin

Java is the undisputed king of Enterprise Software Development. In recent times, Java has become the target of much criticism: It is verbose, needs lots of boilerplate coding, prone to accidental complexity. However, there is little argument about the Java Virtual Machine (JVM). JVM is a masterpiece of Software Engineering and offers a battle-hardened runtime that has passed the test of time.

Over the years, JVM languages like Scala tried to answer the shortcomings of Java and wanted to be better Java but failed. Finally, in Kotlin, it looks like a search for better Java is over. Jet Brains (the company behind the popular IDE IntelliJ) has developed Kotlin, which runs on JVM and answers the shortcomings of Java and offered many modern features. The best part is that unlike Scala, Kotlin is much simpler than Java and offers Go or Python-like developer productivity in JVM.

Google has declared Kotlin as a first-class language to develop Android and boosted Kotlin’s acceptance in the community. Also popular Java Enterprise framework Spring has started to support Kotlin in the Spring eco-system since 2017. I have used Kotlin with Reactive Spring and the experience was amazing.

Main Features:

  • The USP of Kotlin is its language design. I always view Kotlin as Go/Python on JVM because of its clean, concise code. As a result, Kotlin is highly productive.
  • Like many other modern languages, Kotlin offers features like Null pointer safety, Type Inference.
  • As Kotlin also runs in JVM, you can use the existing huge eco-system of Java libraries.
  • Kotlin is a first-class language to develop Android App and has already surpassed Java as the number one programming language to develop Android App.
  • Kotlin is backed by JetBrains and Open Source. So, Kotlin has excellent tooling support.
  • There are two interesting projects: Kotlin Native (to compile Kotlin into native code) and kotlin.js (Kotlin to JavaScript). If they become successful, then Kotlin can be used outside JVM.
  • Kotlin also offers a simple way to write DSL (Domain Specific Language)

Popularity:

Since its first release in 2015, the popularity of Kotlin is soaring. As per Stack Overflow, Kotlin is the fourth most loved Programming language in 2019:

Kotlin is also one of the fastest-growing programming languages and ranked 4th:

Source: Github Octoverse

Popular programming language ranking website PyPl has ranked Kotlin as the 12th most popular programming language with a high upward trend:

Source: Pypl

Since Google has declared Kotlin as the first-class language to develop Android Apps, Kotlin has experienced a huge positive boost in trending as shown below:

Source: Google Trends

Main Use Cases:

  • Enterprise Application
  • Android App Development

Main competitor Languages:

  • Java
  • Scala
  • Python
  • Go
TypeScript

JavaScript is an excellent language but pre-2015 JavaScript had many shortcomings. Even noted Software Engineer Douglas Crockford has written a book “JavaScript: The Good Parts” and implied that JavaScript has bad parts and ugly parts. With no modularization and with “Callback Hell”, developers did not like to maintain especially large JavaScript projects.

Google even developed a platform to transcompile Java code to JavaScript code (GWT). Many companies or people tried to develop better JavaScript e.g. CoffeeScript, Flow, ClojureScript. But TypeScript from Microsoft arguably hit the Jackpot. A group of engineers in Microsoft, led by famous Anders Hejlsberg (creator of Delphi, Turbo Pascal, C#), created TypeScript as a Statically Typed, Modular superset of JavaScript.

TypeScript is transcompiled to JavaScript during compilation. First released in 2014, it quickly attracted the attention of the community. Google was also planning to develop a Statically Typed superset of JavaScript back then. Google was so impressed by TypeScript that instead of developing a new language, they co-operated with Microsoft to improve TypeScript.

Google has used TypeScript as the main programming language for its SPA framework Angular2+. Also, the popular SPA framework React offers support for TypeScript. The other popular JavaScript framework Vue.js has declared that they will use TypeScript to develop the new Vue.js 3:

Source: Vue.js Roadmap

Also, node.js creator Ryan Dahl has decided to use TypeScript to develop a secure Node.js alternative, Deno.

Key Features:

  • Like Go or Kotlin in the list, the principal feature of TypeScript is the language design. With its crisp and clean code, it is one of the most elegant programming languages out there. In terms of Developer productivity, it is on par with Kotlin on JVM or Go/Python. TypeScript is the most productive JavaScript superset hands down.
  • TypeScript is a strongly typed superset of JavaScript. It is especially suited for large Projects and rightly termed as “JavaScript that Scales”.
  • The “Big Three” Single Page Application framework (Angular, React, Vue.js) offers excellent support for TypeScript. In Angular, TypeScript is the preferred programming language. In React and Vue.js, TypeScript is getting increasingly popular.
  • Two of the biggest Tech Giants: Microsoft and Google are working together to develop TypeScript supported by a vibrant open source community. As a result, the tooling support for TypeScript is one of the best.
  • As TypeScript is a superset of JavaScript, it can runs where JavaScript runs: everywhere. TypeScript can run on Browser, Server, Mobile Devices, IoT devices, and Cloud.

Popularity:

Developers love TypeScript for its elegant language design. In the Stackoverflow Developer survey, it has ranked joint second with Python in the most beloved language category:

Source: Stackoverflow

TypeScript is one of the fastest-growing Web programming languages and ranked fifth according to GitHub Octoverse:

Source: Octoverse

TypeScript also entered the Top 10 List (ranked 7th) according to the GitHub Contributions:

Source: Octoverse

TypeScript is having more and more eyeballs in each passing year, which is reflected by Google Trends:

Source: Google Trends

Main Use Cases:

  • Web UI Development
  • Server-side development

Main competitor Languages:

  • JavaScript
  • Dart
Swift

Steve Jobs has refused to support Java (and JVM) in iOS as he has famously quoted that Java is no more a major programming language. We now know that Steve Jobs was wrong with his assessment with Java, but iOS is still not supporting Java. Instead, Apple has chosen Objective-C as the first-class programming language in iOS. Objective-C is a hard language to master. Also, it does not support the high developer productivity required by modern programming languages.

In Apple, Chris Lattner and others have developed Swift as a multi-paradigm, general-purpose, compiled programming language that gives an alternative to Objective-C. The first stable version of Swift was released in 2014. Swift also supports LLVM compiler Toolchain (also developed by Chris Lattner). Swift has excellent interoperability with Objective-C codebase and has already established itself as the main programming language in iOS App development.

Main Features:

  • One of the killer features of Swift is its language design. With simpler, concise and clean syntax, it offers a more productive alternative to Objective-C.
  • Swift also offers features of modern program languages: null safety. Also, it offers syntactic sugar to avoid the “Pyramid of Doom”.
  • As a compiled language, Swift is as fast as C++.
  • Swift has support for LLVM Compiler Toolchain. So, we can use Swift in server-side programming or even Browser programming (using WebAssembly).
  • Swift offers Automatic Reference Counting (ARC) support and thus limits memory mismanagement.

Popularity:

Developers love Swift programming language like many other modern languages. According to the StackOverflow survey, Swift ranked 6th as the most beloved programming language:

In the programming language ranking of TIOBE, Swift has moved to the number 10 ranking in 2019. Considering how young the language is (5 years), this is quite a feat:

Source: TIOBE Index

Google trends also show a sharp rise in the Popularity of Swift followed by a slight decrease in trending in the last couple of saturation:

Source: Google Trends
Main Use Cases:

  • iOS App Development
  • System Programming
  • Client-side development (via WebAssembly)

Main competitor Languages:

  • Objective-C
  • Rust
  • Go
Dart

Dart is the second programming language in this list made by Google. Google is a key player in the Web and Android domain, and it is no surprise that Google has developed its own programming language in the Web and App Development domain. Led by the famous Danish Software Engineer Lars Bak (who lead the Development of Chrome’s V8 JavaScript Engine), Google has released Dart in 2013.

Dart is a general-purpose programming language which supports Strong typing and Objected-Oriented programming. Dart can also be transcompiled to JavaScript and can run where JavaScript runs means virtually everywhere (e.g. Web, Mobile, Server).

Main Features:

  • Like other Google language Go, Dart also heavily focuses on developer productivity. Dart is hugely productive and loved by developers because of its clean, simple, terse syntax.
  • Dart also offers Strong Typing and Object-Oriented programming. Dart is also a second language in this list which fits the ‘Scalable JavaScript’ tag.
  • Dart is one of the few languages which supports JIT compilation (compilation during Runtime) and AOT compilation (compilation during creation time). Thus Dart can target JavaScript runtime (V8 Engine) and Dart can be compiled to fast Native code (AOT compilation)
  • The Cross-Platform Native App Development platform Flutter has chosen Dart as the programming language to develop both iOS and Android App. Since then, Dart has become more popular.
  • Like other Google programming language Go, Dart also has excellent Tooling support and the huge Ecosystem of Flutter. The increasing popularity of Flutter will only increase the adoption of Dart.

Popularity:

According to Github Octoverse, Dart is the fastest-growing Programming language in 2019 and its popularity has five-folded in last year:

According to the TIOBE index, Dart stands 23rd position and has already surpassed many other existing and modern programming languages in only 4 years:

Source: TIOBE index

It is also one of the most beloved programming languages as ranked 12th in StackOverflow developer Survey:

Source: StackOverflow

Along with Flutter, Dart has also experienced huge traction in the last two years as clear by Google Trends:

Source: Google Trends

Main Use Cases:

  • App Development
  • UI Development

Main competitor languages:

  • JavaScript
  • TypeScript
Julia

Most of the programming languages in this list are developed by large corporations except Julia. In technical computing, usually dynamic languages like Python, Matlab are used. These languages offer easy-to-use syntax but are not fit for large-scale Technical computation. They use C/C++ libraries for the CPU intensive tasks which gives the famous Two-Language problem as they need Glue Code to bind both languages. As Code is translated between two languages, there is always some performance loss.

To tackle the issue, a group of researchers at MIT planned to create a new language from the ground-up which takes the advantages of modern hardware and combines the best parts of other languages. They work in the MIT innovation lab with the following Manifesto:

Source: Julia Presentation

Julia is a dynamic, high-level programming language that offers first-class support for Concurrent, Parallel and Distributed Computing. The first stable version of Julia is released in 2018 and soon got the attraction of the community and industry. Julia can be used in Scientific Computing, Artificial Intelligence, and many other fields and can finally solve the “Two-Language” problem.

Features:

  • Like Rust, the key feature of Julia is the design of the languages. It tries to combine some of the best features of the existing programming language in high performance and Scientific computing without sacrificing performance. Until now it has done a great job.
  • Julia is a dynamic programming language with optionally typed. Thus, Julia is easy to learn a programming language and highly productive.
  • It uses multiple-dispatch programming paradigm at its core.
  • It has built-in support for Concurrent, Parallel and Distributed Computing.
  • It also offers asynchronous I/O for I/O intensive tasks.
  • It is Blazingly fast and can be used in Scientific Computing where millions of threads are required.

Popularity:

Julia mainly competes with Python in many areas. As Python is one of the most popular programming languages, it will take a few years until Julia will be mainstream.

Julia is relatively new (only one-year-old) but still ranks 43rd in the TIOBE index:

Source: TIOBE

Google Trends also shows a stable interest in Julia over the years.

But considering the feature set and the number of companies is working behind Julia such as NSF, DARPA, NASA, Intel, it is just a matter of when instead of if for Julia to make a breakthrough:

Main Use Cases:

  • Scientific Computing
  • High-performance computing
  • Data Science
  • Visualization

Main competitor languages:

  • Python
  • Matlab

Originally published by Md Kamaruzzaman at https://towardsdatascience.com

Rust vs. Go: Should I Rust, or Should I Go

Rust vs. Go: Should I Rust, or Should I Go

Well both Rust and Go provide amazing performance. Should you write you’re next big thing with Rust or with Go? Go is fast and powerful, but it avoids bogging the developer down, focusing instead on simplicity and uniformity. Rust. If on the other hand, wringing out every last ounce of performance is a necessity, then Rust should be your choice. Rust is more of a competitor to C++ than it is with Go.

Should I stay, or should I go?” Great song by the band The Clash. I’m listening to it, right now, while I’m writing this article. The song debuted back in 1982, a long time ago. Back then, I was just a kid exploring a new hobby — programming my Atari 2600. The first video game I ever wrote was written using 6502 Assembly for that console. The compiler for it cost about $65, if I recall, which at the time equated to mowing ~13 or so lawns.

The game was simple: using the joystick, maneuver your spaceship through a randomly generated scrolling cave. The cave walls were sinusoidal, scrolling vertically on the left and right sides of the screen, and you had to make sure your craft didn’t crash into them. I know, I know: Not that sophisticated. But I was only ten or eleven years old at the time.

Despite the “power” of the processor, computing sine values at run-time was simply too much for it. So, using my handy Texas Instruments calculator, I pre-calculated a bunch of the sine values, carefully writing them down on paper, and then entering them in as constants for the game. This greatly enhanced the performance of the game, and made it usable.

So what’s my point? What’s any of this got to do with Rust or Go?

Today’s languages are far more advanced than 6502 Assembly, which make it easier to write complex programs. It took a lot of my time to write that game, and I could do it much faster today, with less code than I did back then. But which language today provides that magic combination of simplicity and power?

Well both Rust and Go provide amazing performance. They both compile to machine code, the Holy Grail of performance. And with today’s processing power, developers can do amazing things with either of these languages. So the question is: Should you write you’re next big thing with Rust or with Go?

With a quick search, you can easily find several articles that go into detail about the differences between the two languages. But the focus of this article is the bang for the buck, that magic combination of performance per line of code.

To put it another way, where is that sweet spot of simple code and top-end performance? And in this case, is it Rust, or is it Go?
There really isn’t any argument: Rust is faster than Go. In the benchmarks above, Rust was faster, and in some cases, an order of magnitude faster.

But before you run off choosing to write everything in Rust, consider that Go wasn’t that far behind it in many of those benchmarks, and it’s still much faster than the likes of Java, C#, JavaScript, Python and so on. So in other words, it’s almost a wash between Rust and Go on the axis of performance. Now, if what you’re building needs to wring out every last ounce of performance, then by all means, choose Rust. But if what you need is top-of-the-line performance, then you’ll be ahead of the game choosing either of these two languages.

So then we’re down to the complexity of the code. This is where things can be muddy since this can be more subjective than performance benchmarks. Let’s look at a simple exercise: building a small web server that prints out “Hello World” when it receives an HTTP request. To do this in Rust, it looks something like this:

use std::net::{TcpStream, TcpListener};
use std::io::{Read, Write};
use std::thread;


fn handle_read(mut stream: &TcpStream) {
    let mut buf = [0u8; 4096];
    match stream.read(&mut buf) {
        Ok(_) => {
            let req_str = String::from_utf8_lossy(&buf);
            println!("{}", req_str);
            },
        Err(e) => println!("Unable to read stream: {}", e),
    }
}

fn handle_write(mut stream: TcpStream) {
    let response = b"HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n<html><body>Hello world</body></html>\r\n";
    match stream.write(response) {
        Ok(n) => println!("Response sent: {} bytes", n),
        Err(e) => println!("Failed sending response: {}", e),
    }
}

fn handle_client(stream: TcpStream) {
    handle_read(&stream);
    handle_write(stream);
}

fn main() {
    let port = "8080";
    let listener = TcpListener::bind(format!("127.0.0.1:{}", port)).unwrap();
    println!("Listening for connections on port {}", port);

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(|| {
                    handle_client(stream)
                });
            }
            Err(e) => {
                println!("Unable to connect: {}", e);
            }
        }
    }
}

Something pretty similar in Go looks like this:

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
)

type handler struct{}

func (theHandler *handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	log.Printf("Received request: %s\n", request.URL)
	log.Printf("%v\n", request)
	io.WriteString(writer, "Hello world!")
}

const port = "8080"

func main() {
	server := http.Server{
		Addr:    fmt.Sprintf(":%s", port),
		Handler: &handler{},
	}

	server.ListenAndServe()
}

Now, they are not 100% exactly the same, but they are close enough. The difference between them is ~20 lines of code. Rust definitely forces the developer to consider more, and thus write more code than Go.

Another example: Consider one of the more difficult aspects of software development: multi-threading. When tackling something like this, as you undoubtedly would when building an HTTP server, there’s a lot to think about:

  • You need to ensure everything you design is thread safe (locks)
  • You need to handle communication between threads (channels)
  • You have to design with concurrency and parallelism in mind (threads and routines)

Both Rust and Go handle these hurdles really efficiently, but Go requires less effort. With Rust, you have way more options, and thus more power, when spawning threads. Just look at some of the documentation on this. Here’s just one way to spawn a thread in Rust:

use std::thread;

let handler = thread::spawn(|| {
    // thread code
});

handler.join().unwrap();

On the other hand, here’s how to create something similar using Go:

go someFunction(args)

Another crucial part of writing code is handling errors. Here I think Rust and Go are quite similar. Rust enables the developer to handle errors cases through the use of the enum return types: Option<T>and Result<T, E>. The Option<T> will return either None or Some(T) whereas Result<T, E> will return either Ok(T) or Err(T). Given that most of Rust’s own libraries, as well as other third-party libraries, return one of these types, the developer will usually have to handle the case where nothing is returned, or where an error is returned.

Here’s a simple example of the Result type being returned by a function in Rust:

fn foo_divide(a: f32, b: f32) -> Result<f32, &'static str> {
    if b == 0.0 {
        Err("divide by zero error!")
    } else {
        Ok(a / b)
    }
}fn main() {
    match foo_divide(5.0, 4.0) {
        Err(err) => println!("{}", err),
        Ok(result) => println!("5 / 4 = {}", result),
    }
}

Notice that the Err case must be handled within the match statement.

Go, on the other hand, leaves this more up to the developer, since errors can be ignored using the _. However, idiomatic Go strongly recommends returning an error, especially since functions in Go can return multiple values. Therefore, it’s easy to have functions return their intended value along with an error, if there is one.

Here is the corresponding example from above done in Go:

func fooDivide(a float32, b float32) (float32, error) {
    if b == 0 {
        return 0, errors.New("divide by zero error!")
    }    return a / b, nil
}func main() {
    result, err := fooDivide(5, 4)
    if err != nil {
       log.Printf("an error occurred: %v", err)
    } else {
       log.Printf("The answer is: 5 / 4 = %f", result)
    }
}

Notice that this line:

result, err := fooDivide(5, 4)

could have been written as

result, _ := fooDivide(5, 4)

In the latter case, the error returned would have been ignored.

Honestly, they’re both pretty similar, except for Rust forcing error checking. Otherwise, there’s little difference, and it’s difficult to find an advantage one has over the other. To my eyes, this is a draw.

I could keep going, digging deeper into other language differences. But the bottom line, from threads, to channels, to generics, Rust provides the developer with more options. In this respect, Rust is closer to C++ than Go. Does this make Rust inherently more complex?

I think so, yes.

So here are my recommendations:

  • Either. If you’re building a web service that handles high load, that you want to be able to scale both vertically and horizontally, either language will suit you perfectly.
  • Go. But if you want to write it faster, perhaps because you have many different services to write, or you have a large team of developers, then Go is your language of choice. Go gives you concurrency as a first-class citizen, and does not tolerate unsafe memory access (neither does Rust), but without forcing you to manage every last detail. Go is fast and powerful, but it avoids bogging the developer down, focusing instead on simplicity and uniformity.
  • Rust. If on the other hand, wringing out every last ounce of performance is a necessity, then Rust should be your choice. Rust is more of a competitor to C++ than it is with Go. Having battled with C++, Rust feels just as powerful but with many happy improvements. Rust empowers developers to have control over every last detail of how their threads behave with the rest of the system, how errors should be handled, and even the lifetime of their variables!
  • Rust. Rust was designed to interoperate with C. Go can as well, but gives up a lot to achieve this goal, and it’s not really its focus.
  • Go. If readability is a requirement, go with Go. It’s far too easy to make your code hard for others to grok with Rust.

I hope you enjoyed reading this!

Learning Rust with TypeScript

Learning Rust with TypeScript

Today I’m starting a new tutorial series about "Learning Rust by Contrasting with TypeScript". It’s a complete beginner’s guide to start learning Rust with TypeScript.

Today I’m starting a new tutorial series about "Learning Rust by Contrasting with TypeScript". It’s a complete beginner’s guide to start learning Rust with TypeScript.

Tutorials in the Series

  1. Part 1 - Getting Started.
  2. Part 2 - We explore variables, mutability, and data types
  3. Part 3 - Diving into the complexities of ownership
  4. Part 4 - The power of references
  5. Part 5 - Structuring with structs.
  6. Part 6 - Exploring the unexpected power of enums.
  7. Part 7 - Exploring Rust’s module system.
  8. Part 8 - Looking at collections.
  9. Part 9 - Error handling and generic data types.
Learning Rust with TypeScript: Part 1 - Getting Started

Motivation

Watch the video.

Prerequisites

The common requirement for both the Rust and TypeScript projects is the GIT version control system.

Rust Installation

Rust installation involves installing global binaries, e.g., rustc and cargo. This article was written using version 1.38.0.

TypeScript Installation

TypeScript depends on the JavaScript runtime Node.js; must first be installed. This installation involves installing global binaries, e.g., node and npm. This article was written using version 12.13.0.

TypeScript itself is installed as a Node.js project dependency as we will see below.

Rust Project Creation

Project setup involves running the cargo command to scaffold a Rust project:

note: The completed project is available for download.

cargo new r00_hello_world

The project is also initialized as a GIT repository with a .gitignore file. It also includes the sample source file: src/main.rs:

fn main() {
    println!("Hello, world!");
}

TypeScript Project Creation

Project setup first requires scaffolding a JavaScript Node.js project by creating a folder and using the npm command:

note: The completed project is available for download.

mkdir t00_hello_world
cd t00_hello_world
npm init --yes

We initialize the project as a GIT repository with:

git init

and create a .gitignore file:

node_modules

We then convert the JavaScript Node.js project into TypeScript by first installing the TypeScript development dependency:

npm install -D typescript

We then create a TypeScript configuration file: tsconfig.json. Here we use the microsoft/TypeScript-Node-Starter example.

We update the .gitignore file to ignore the output directory: dist:

node_modules
dist

We update the package.json; updating the main value and adding a start and build to scripts:

{
  "name": "t00_hello_world",
  "version": "1.0.0",
  "description": "",
  "main": "dist/index.js",
  "scripts": {
    "start": "node dist/index.js",
    "build": "tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^3.6.4"
  }
}

Finally, we create the source file: src/index.ts:

console.log('Hello World!');

Rust Build

During development, we use the following command to build a Rust project:

cargo build

The output is a compiled OS executable: target/debug/r00_hello_world.

For release, an optimized build, we use the following command:

cargo build --release

The output is a compiled OS executable: target/release/r00_hello_world.

TypeScript Build

We use the following command to build a TypeScript project:

npm run build

The output is a transpiled JavaScript file: dist/index.js.

Rust Execute

We simply execute the compiled OS executable as we would any other:

./target/debug/r00_hello_world

We can also use the command:

cargo run

to both build and run.

TypeScript Execute

We execute the transpiled JavaScript file using the npm script:

npm start

or we can execute it directly:

node dist/index.js

In either case, we are executing our project on top of the JavaScript runtime.

Seeing Rust as a potential successor to TypeScript, we go through through the Rust Bookwith TypeScript in mind.

Learning Rust with TypeScript: Part 2 - We explore variables, mutability, and data types.

Variables and Mutability

Let us walk through the examples in the Rust Book Variables and Mutability section and contrast them with TypeScript.

With Rust, a variable set with let as shown is immutable.

...
fn main() {
    // IMMUTABLE
    let x = 5;
    println!("The value of x is: {}", x); // 5
    // x = 6; // CANNOT ASSIGN TWICE TO IMMUTABLE VARIABLE 
    // println!("The value of x is: {}", x);
    ...
}

In TypeScript, the equivalent is a variable set with const.

...
// IMMUTABLE
const x = 5;
console.log(`The value of x is: ${x}`); // 5
// x = 6; // CANNOT ASSIGN BECAUSE CONSTANT
// console.log(`The value of x is: ${x}`)
...

note: In both Rust and TypeScript the variable types are inferred from the right-hand-side; thus explicit typing is not required.

With Rust, a variable set with let mut as shown is mutable.

...
fn main() {
    ...
    // MUTABLE
    let mut y = 5;
    println!("The value of y is: {}", y); // 5
    y = 6;
    println!("The value of y is: {}", y); // 6
    ...
}

In TypeScript, the equivalent is a variable set with let.

...
// MUTABLE
let y = 5;
console.log(`The value of y is: ${y}`); // 5
y = 6;
console.log(`The value of y is: ${y}`); // 6
...

With Rust, a variable set with let and let mut can be set at run-time:

fn another_function(x: i32) -> i32 {
    return x + 1;
}

fn main() {
    ...
    // RUN-TIME ASSIGNMENT
    let z = another_function(5);
    println!("The value of z is: {}", z); // 6
    let mut zz = another_function(5);
    zz = zz + 1;
    println!("The value of zz is: {}", zz); // 7
    ...
}

Likewise for TypeScript for variables set with both const and let:

const anotherFunction = (x: number): number => x + 1;
...
// RUN-TIME ASSIGNMENT
const z = anotherFunction(5);
console.log(`The value of z is: ${z}`); // 6
let zz = anotherFunction(5);
zz = zz + 1;
console.log(`The value of zz is: ${zz}`); // 7
...

Rust has another immutable variable type; const. Unlike, let however, const cannot be set at run-time (only set at compile-time). The upper-case const variable names is by convention.

n another_function(x: i32) -> i32 {
    return x + 1;
}

fn main() {
    ...
    // CONSTANT
    const MAX_POINTS: i32 = 100000;
    println!("The value of MAX_POINTS is: {}", MAX_POINTS); // 100000
    const ANOTHER_CONSTANT: i32 = 100000 + 1; // COMPILE-TIME ASSIGNMENT
    println!("The value of ANOTHER_CONSTANT is: {}", ANOTHER_CONSTANT); // 100001 
    // const YET_ANOTHER_CONSTANT: i32 = another_function(1); // CANNOT RUN-TIME ASSIGNMENT
    // println!("The value of YET_ANOTHER_CONSTANT is: {}", YET_ANOTHER_CONSTANT);
    ...
}

TypeScript does not have an equivalent variable type. For this situation, one typically uses the TypeScript const variable type and simply name it with upper-case by convention.

Rust allows one to redefine, or shadow, a variable of type let:

...
fn main() {
    ...
    // SHADOWING
    let a = 5;
    let a = a + 1;
    let a = a * 2;
    println!("The value of a is: {}", a); // 12
}

note: It is important to observe that the resultant variable a can still be immutable. Also, while not illustrated in this example, the variable types can also differ through shadowing.

TypeScript does not have an equivalent feature, i.e., TypeScript cannot redeclare a block-scoped variable. In TypeScript, one often simply creates a new const variable name in this situation.

Data Types

Now we walk through the examples in the Rust Book Data Typessection; starting first with the Rust scalar types.

...
fn main() {
    ...
    // NUMBERS
    let i = 1; // i32
    println!("The value of i is: {}", i); // 1
    let j = 1.1; // f64
    println!("The value of j is: {}", j); // 1.1

    // BOOLEAN
    let b = true; // bool
    println!("The value of b is: {}", b); // true

    // CHARACTER
    let c = 'a'; // char
    println!("The value of c is: {}", c); // a
    ...
}

Let us consider the equivalents (called primitives) in TypeScript:

...
// NUMBERS
const i = 1; // number
console.log(`The value of i is: ${i}`); // 1
const j = 1.1 // number
console.log(`The value of j is: ${j}`); // 1.1

// BOOLEAN
const b = true; // boolean
console.log(`The value of b is: ${b}`); // true

// CHARACTER
const c = 'a'; // string
console.log(`The value of c is: ${c}`); // a
...

One big difference is that in JavaScript (and thus TypeScript) all numbers are 64-bit floating point where-as Rust there are a number of integer and floating point types.

Another difference is that JavaScript (and thus TypeScript) has a primitive string type that can contain an arbitrary number of characters; not just a single character.

Rust has two compound types; tuple and array. A tuple being a fixed length ordered list of values of varying types. An array being a fixed length ordered list of values of the same type.

...
fn main() {
    ...
    // TUPLE
    let tup = (0, 'a', 1.1); // (i32, char, f64)
    println!("The second value of tup is: {}", tup.1); // a
    let (t1, t2, t3) = tup;
    println!("The value of t1 is: {}", t1); // 0
    println!("The value of t2 is: {}", t2); // a
    println!("The value of t3 is: {}", t3); // 1.1

    // ARRAY
    let arr = [0, 1, 2]; // [i32, 3]
    println!("The second value of arr is: {}", arr[1]); // 1
    let [a1, a2, a3] = arr;
    println!("The value of a1 is: {}", a1); // 0
    println!("The value of a2 is: {}", a2); // 0
    println!("The value of a3 is: {}", a3); // 0
    ...
}

In TypeScript, a tuple is essentially the same as in Rust. An array, however is of arbitrary length, e.g., we can append a value.

...
// TUPLE
const tup: [number, string, number] = [0, 'a', 1.1]; // [number, string, number]
console.log(`The second value of tup is: ${tup[1]}`); // a
const [t1, t2, t3] = tup;
console.log(`The value of t1 is: ${t1}`); // 0 
console.log(`The value of t2 is: ${t2}`); // a
console.log(`The value of t3 is: ${t3}`); // 1.1

// ARRAY
const arr = [0, 1, 2]; // number[]
console.log(`The second value of arr is: ${arr[1]}`); // 1
const [a1, a2, a3] = arr;
console.log(`The value of a1 is: ${a1}`); // 0 
console.log(`The value of a2 is: ${a2}`); // 1
console.log(`The value of a3 is: ${a3}`); // 2
arr[3] = 3;
console.log(`The fourth value of arr is: ${arr[3]}`); // 3
...

Sidebar into Object or Reference

So far, Rust and TypeScript have been relatively similar; however there is a big difference lurking in compound types. The difference is in how each of them store compound types in variables.

In Rust, the value of a compound type variable is the object itself.

...
fn main() {
    ...
    // OBJECT OR REFEREENCE
    let mut tup2 = (0, 'a', 1.1); // (i32, char, f64)
    let tup3 = tup2;
    tup2.0 = 1;
    println!("The first value of tup2 is: {}", tup2.0); // 1
    println!("The first value of tup3 is: {}", tup3.0); // 0

    let mut arr2 = [0, 1, 2]; // [i32, 3]
    let arr3 = arr2;
    arr2[0] = 1;
    println!("The first value of arr2 is: {}", arr2[0]); // 1
    println!("The first value of arr3 is: {}", arr3[0]); // 0
}

Observations:

  • We have to use let mut for tup2 in order to mutate the value later
  • Assigning tup2 to tup3 (copying their value) creates a completely new tuple
  • The result is that mutating tup2 has no relevance to tup3
  • Some logic applies to arrays

On the other hand, in TypeScript, the value of a compound type is a reference to the object.

note: In TypeScript, primitive types (boolean, number, string) behave like Rust with the value of the variable being the object itself.

...
// OBJECT OR REFERENCE
const tup2: [number, string, number] = [0, 'a', 1.1]; // [number, string, number]
const tup3 = tup2;
tup2[0] = 1;
console.log(`The first value of tup2 is ${tup2[0]}`); // 1
console.log(`The first value of tup3 is ${tup3[0]}`); // 1

const arr2 = [0, 1, 2]; // number[]
const arr3 = arr2;
arr2[0] = 1;
console.log(`The first value of arr2 is ${arr2[0]}`); // 1
console.log(`The first value of arr3 is ${arr3[0]}`); // 1
...

Observations:

  • We can use const for tup2 because we are mutating the object and not the reference
  • Assigning tup2 to tup3 (copying their value) copies the reference to the same tuple object
  • The result that mutating tup2 is really mutating the common tuple object referenced by both tup2 and tup3
  • Same logic applies to arrays

I can already see that this is going to take me some getting used to as I have the TypeScript pattern etched into my brain.

Addendum 11/8/19: Having written a couple of more articles, I have come to realize (believe it was buried in the Rust book too), that the compound types tuple and array are likely only to be used as constants, e.g., days of the week, and as such this difference between Rust and TypeScript is irrelevant. Specifically, we will likely be using the Rust Vec type as the equivalent to TypeScript arrays.

Control Flow

The core concepts around flow of control are virtually identical between Rust and TypeScript; confirming this by walking through examples in the Rust Book Control Flowsection.

fn another_function() {
    println!("Another function.");
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

fn abs(x: i32) -> i32 {
    if x >= 0 {
        return x;
    }
    x * -1
}

fn main() {
    // FUNCTIONS
    another_function();
    let x = 0;
    let y = plus_one(x);
    println!("The value of y is: {}", y); // 1
    let a = -1;
    let b = abs(a);
    println!("The value of b is: {}", b); // 1

    // COMMENTS
    /*
    Rust supports
    mult-line comments.
    */

    // IF EXPRESSIONS
    let number = 3;
    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }

    let number = 6;
    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }

    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };
    println!("The value of number is: {}", number);

    // LOOPS
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2;
        }
    };
    println!("The result is {}", result);

    let mut number = 3;
    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }
    println!("LIFTOFF!!!");

    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("the value is: {}", element);
    }
}

The same implemented in TypeScript:

const anotherFunction = () => console.log('Another funciton.');

const plusOne = (x: number): number => x + 1;

const abs = (x: number): number => {
  if (x >= 0) {
    return x;
  }
  return x * -1;
}

// FUNCTIONS
anotherFunction();
const x = 0;
const y = plusOne(x);
console.log(`The value of y is: ${y}`); // 1
const a = -1;
const b = abs(a);
console.log(`The value of b is: ${b}`); // 1

// COMMENTS
/*
TypeScript supports
multi-line comments.
*/

// IF EXPRESSIONS
const number = 3;
if (number < 5) {
  console.log('condition was true');
} else {
  console.log('condition was false');
}

const number2 = 6;
if (number2 % 4 === 0) {
  console.log('number2 is divisible by 4');
} else if (number2 % 3 === 0) {
  console.log('number2 is divisible by 3');
} else if (number2 % 2 === 0) {
  console.log('number2 is divisible by 2');
}

const condition = true;
const number3 = condition ? 5 : 6;
console.log(`The value of number3 is: ${number3}`);

let counter = 0;
let result: number;
while (true) {
  counter += 1;
  if (counter === 10) {
    result = counter * 2;
    break;
  }
}
console.log(`The result is ${result}`);

let number4 = 3;
while (number4 !== 0) {
  console.log(`${number4}!`);
  number4 -= 1;
}
console.log('LIFTOFF!!!');

const arr = [10, 20, 30, 40, 50];
arr.forEach(element => console.log(`the value is: ${element}`));

Observations:

  • The Rust pattern of having the last line of a function being an expression (with no semi-colon) to mean the return value was novel to me
  • Having gotten used to the TypeScript ternary operator, found the Rust equivalent a bit verbose
  • With Rust, having a loop return a value is novel to me

The code for this article is available to download; Rust download and TypeScript download.

Learning Rust with TypeScript: Part 3 - Diving into the complexities of ownership.

Let us walk through the examples in the Rust Book What is Ownership?section and contrast them with TypeScript.

note: Unlike the previous articles, reading the Rust Book on this topic first is a must (and a pleasure).

Variable Scope

First, we remind ourselves how variable scope works in Rust:

fn do_something() {
    let x = 5; // x comes into scope
    println!("The value of x is: {}", x); // 5
} // x goes out of scope
...
fn main() {
    do_something();
    ...
}

Here we would say that the value of 5 is uniquely owned by the variable x. Once x goes out of scope, Rust immediately invalidates the data (the 5) owned by it; in this case the value is removed from the stack.

note: The scalar (i32, bool, etc) and primitive compound types (tuple and array of scalars) are examples of values that are stored on the stack; apparently this is related to these types have the Copy trait (something that we will explore later).

Let us interpret what is happening with the equivalent TypeScript code:

const doSomething = () => {
  const x = 5;
  console.log(`The value of x is: ${x}`); // 5
}

doSomething();

Looking at the code, might assume that once the doSomething function executes the value of 5 can be immediately reclaimed. But in reality, this is something that is handled by the JavaScript garbage collector and thus outside of our domain of knowledge.

note: Remember, TypeScript is transpiled to JavaScript before it is executed.

Some high-level languages, such as JavaScript, utilize a form of automatic memory management known as garbage collection (GC). The purpose of a garbage collector is to monitor memory allocation and determine when a block of allocated memory is no longer needed and reclaim it. This automatic process is an approximation since the general problem of determining whether or not a specific piece of memory is still needed is undecidable.

— MDN — Memory Management JavaScript

The String Type

We now are introduced to Rust’s String type:

...
fn do_something_else() {
    let s = String::from("hello"); // s comes into scope
    println!("The value of s is: {}", s); // hello
} // s goes out of scope

fn main() {
    ...
    do_something_else();
}

Observations:

  • Since Strings are mutable, their associated data is split into the variable’s value, a pointer stored on the stack and the pointed to data stored on the heap
  • Here I struggled a bit; but I think we would say that both the pointer value and the heap data are uniquely owned by the variable
  • The String type does not have the Copy trait; rather it has the Drop trait (not sure what this exactly means but apparently we will learn more about this later).
  • When s goes out of scope, the String’s drop feature (we will learn more about this later too) is executed. The data (stack pointer value and heap data) are deallocated

With TypeScript, the string type is, interestingly enough, immutable. As such it is not fully comparable to Rust’s String type; we will come back to TypeScript strings in a bit. Rather, we can consider the TypeScript array type:

...
const doSomethingElse = () => {
  const arr = [0, 1, 2];
  console.log(`The first value of arr is: ${arr[0]}`); // 0
}
...
doSomethingElse();

Observations:

  • The array type is mutable
  • In TypeScript we would say the value of arr is a reference to the array object; essentially means the same as the concept of a pointer with the Rust String example
  • Again, we are reliant on the JavaScript garbage collector to reclaim memory

note: In Rust, the term reference has a special meaning. That is why we use the more generic term pointer.

Mutable with Pointer Values

Let us explore how to mutate a Rust String:

...
fn do_even_more() {
    let s = String::from("hello");
    // s.push_str(", world!"); // CANNOT BORROW AS MUTABLE
    println!("The value of s is: {}", s); // hello
    let mut t = String::from("hello");
    t.push_str(", world!"); // CANNOT BORROW AS MUTABLE
    println!("The value of t is: {}", t); // hello, world!
}

fn main() {
    ...
    do_even_more();
    ...
}

Observations:

  • In order to mutate a String, the variable must be a let mut

Mutating a TypeScript array:

...
const doEvenMore = () => {
  const arr = [0, 1, 2];
  arr.push(3);
  console.log(`The fourth value of arr is: ${arr[3]}`); // 3
}
...
doEvenMore();
...

Observations:

  • TypeScript (and JavaScript for that matter) does not have a similar immutable concept for references, i.e., any reference to an object allows one to operate on it as one pleases

note: In TypeScript, const only prevents one from re-assigning the reference value to another object

Copy Versus Move

Let us explore the meaning for assignment for pointers in Rust:

...
fn do_something_crazy() {
    // COPY
    let x = 5;
    println!("The value of x is: {}", x); // 5
    let y = x;
    println!("The value of y is: {}", y); // 5
    println!("The value of x is: {}", x); // 5
    
    // MOVE
    let s = String::from("hello");
    println!("The value of s is: {}", s); // hello
    let t = s;
    println!("The value of t is: {}", t); // hello
    // println!("The value of s is: {}", s); // BORROW OF MOVED VALUE
}

fn main() {
    ...
    do_something_crazy();
    ...
}

Observations:

  • As we saw in an earlier article assigning variables (of types with Copy trait) to another variable copies the value to the new variable
  • On the other hand, assigning variables (of types with Drop trait) to another variable copies the pointer value, invalidates the source variables pointer value, and moves the ownership of heap data to the target variable

note: This idea of moving the heap data’s ownership is my best interpretation of what is meant by move here.

Let us explore the meaning of assignment for references in TypeScript:

...
const doSomethingCrazy = () => {
  const x = 5;
  console.log(`The value of x is: ${x}`); // 5
  const y = x;
  console.log(`The value of y is: ${y}`); // 5

  const arr = [0, 1, 2];
  console.log(`The first value of arr is: ${arr[0]}`); // 0
  const arr2 = arr;
  console.log(`The first value of arr2 is: ${arr2[0]}`); // 0
  console.log(`The first value of arr is: ${arr[0]}`); // 0
}

...
doSomethingCrazy();
...

Observation:

  • Similar to Rust, assigning a TypeScript primitive variable to another variable copies the value
  • Assigning reference variables to another variable copies the reference value. TypeScript does not enforce any restrictions on the number of references to an object

Ownership and Functions

With Rust, from an ownership perspective, passing a parameter to a function (and returning a value) behaves much like assigning variables.

...
fn makes_copy(some_integer: i32) { 
    println!("The value of some_integer is: {}", some_integer); // 5
} 

fn takes_ownership(some_string: String) { 
    println!("{}", some_string); // hello
} 

fn gives_ownership() -> String { 
    let some_string = String::from("hello");
    some_string
}

fn main() {
    ...    
    // OWNERSHIP AND FUNCTION PARAMETERS
    let x = 5;
    makes_copy(x);
    println!("The value of x is: {}", x); // 5

    let s = String::from("hello");
    takes_ownership(s);
    // println!("The value of s is: {}", s); // BORROW OF MOVED VALUE

    // OWNERSHIP AND FUNCTION RETURNS
    let s1 = gives_ownership();
    println!("The value of s1 is: {}", s1); // hello
}

Likewise for TypeScript; i.e., matches TypeScript variable assignment:

...
const makesCopy = (someNumber: number) => {
  console.log(`The value of someNumber is: ${someNumber}`); // 5
}

const makesReferenceCopy = (someArray: number[]) => {
  console.log(`The first value someArray is: ${someArray[0]}`); // 0
}

const returnsReference = (): number[] => [0, 1, 2];
...
// FUNCTION PARAMETERS
const x = 5;
makesCopy(5);
console.log(`The value of x is: ${x}`); // 5

const arr = [0, 1, 2];
makesReferenceCopy(arr);
console.log(`The first value arr is: ${arr[0]}`); // 0

// FUNCTION RETURNS
const arr1 = returnsReference();
console.log(`The first value arr1 is: ${arr1[0]}`); // 0

The examples from this article are available for download: Rust download and TypeScript download.

Learning Rust with TypeScript: Part 4 - The power of references.

References and Borrowing

Let us walk through the examples in the Rust Book References and Borrowing section and contrast them with TypeScript.

In the previous article, we observed that when we passed a String (pointer) variable into a function, that variable’s pointer value was invalidated and the ownership of the string (heap) data was moved onto the parameter in the function’s scope.

This means that we can no longer use the invalidated variable in the calling function; this is an unfortunate situation.

The solution is that we can instead pass a reference (a pointer to the pointer) into the function. This does not invalidate the variable nor move the ownership of the string data.

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len); // hello 5
}

fn calculate_length(s: &String) -> usize {
    let len = s.len();
    // s.push_str(", world!"); // CANNOT BORROW AS MUTABLE
    len
}

Observations:

  • We can continue to use the s1 variable after passing a reference of it into the calculate_length function
  • Also observe, we cannot use the reference s to mutate the String; the function’s signature guarantees the function cannot mutate the s parameter
  • In this case, we can refer to calculate_length a pure function; no side-effects

TypeScript neither has a concept of object ownership nor of a reference (in the Rust pointer to a pointer sense). Instead, we simply can pass a TypeScript reference into a function without impacting our ability to continue to use that reference in the calling code.

const calculateLength = (arr: number[]): number => {
  const len = arr.length;
  arr.length = 0; // UNEXPECTED SIDE EFFECT
  return len;
}

const arr = [0, 1, 2];
const len = calculateLength(arr);
console.log(`The length of ${arr} is ${len}`); // [] 3

Observations:

  • Notice that the arr variable continues to be usable after passing it into the calculateLength function
  • Also, because TypeScript does not have controls to limit the mutability of referenced objects, we can create unexpected size effects in functions, e.g., in this case calculateLength empties the array (unexpected to the consumer of the function, that is)

Mutable References

In the previous Rust example we could not mutate (purposefully) the String in the calculate_length function. Let us say, however, we wanted to be able to mutate the String? You guessed it, Rust supports mutable references.

fn main() {
    ...
    let mut s = String::from("hello");
    change(&mut s);
    println!("The value of s is: {}.", s); // hello, world
}
...
fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

Observation:

  • Not only do we pass in a mutable reference to the change function, the function’s signature actually requires it

Rust has rules limiting the use of mutable references when other references (mutable or not) are in scope. The general idea is that Rust is preventing one from accidentally mutating a referenced value unexpectedly.

fn main() {
    ...
    let mut s1 = String::from("hello");
    let r1 = &s1;
    let r2 = &s1;
    // let r3 = &mut s1; // CANNOT BORROW MUTABLE SINCE ALREADY BORROWED
    // change(r3);
    // println!("{}, {}, and {}", r1, r2, r3);
    println!("{} and {}", r1, r2); // hello hello
}
...
fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

Observations:

  • While we can create an unlimited number of immutable references, e.g., r1 and r2, we cannot create the mutable r3 reference while other references are in scope
  • Unlike variable scope, the scope of references is between when they are created and last used (a little weird).

In TypeScript, we have no such protections around references; any variable reference essentially allows one to mutate the object and one can create an unlimited number of them.

...
const change = (arr: number[]) => {
  arr.push(100);
}
...
const arr1 = [0, 1, 2];
const r1 = arr1;
const r2 = arr1;
const r3 = arr1;
change(r3);
console.log(`The value of r1 is: ${r1}`); // [0, 1, 2, 100]
console.log(`The value of r2 is: ${r2}`); // [0, 1, 2, 100]
console.log(`The value of r3 is: ${r3}`); // [0, 1, 2, 100]

Dangling References

Rust also includes checks to ensure one does not return dangling references, e.g., a reference to something that no longer exist.

...
/*
fn dangle() -> &String { // RETURNS DANGLING REFERENCE
    let s = String::from("hello");
    &s
}
*/

Tried to think of a TypeScript example of a dangling reference, but then was reminded that the garbage collector only reclaims objects that are not in use, i.e., without reachable references to them.

Slices

Let us walk through the examples in the Rust Book The Slice Typesection and contrast them with TypeScript.

First we learn that we can create a new type of immutable reference to a String: a String slice (indicted as a &str type). Pretty straightforward stuff.

The more interesting feature is that because slices are references, rules around mutations and references apply to protect one from making mistakes:

fn main() {
    ...
    let mut phrase = String::from("hello world!");
    let first = first_word(&phrase);
    println!("The value of first is {}.", first); // hello
    // phrase.clear(); // CANNOT BORROW AS MUTABLE
    println!("The value of first is {}.", first); // hello
}
...
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[..]
}

Observations:

  • Here, as long as first is in scope (through the last line of main), we cannot mutate the phrase.
  • Earlier we indicated that we could not create mutable references once another reference in scope. Along the same line, we also cannot mutate the referenced object once another reference is in scope

In TypeScript we do not have slices and thus have to take a different approach:

...
const firstTwo = (arr: number[]): number[] => arr.slice(0, 2);
...
const arr2 = [0, 1, 2];
const first = firstTwo(arr2);
console.log(`The value of first is: ${first}`); // [0, 1]
arr2.length = 0;
console.log(`The value of first is: ${first}`); // [0, 1]
...

Observations:

  • Here we use the slice method to create a completely new array
  • Creating a completely new array, especially if the array is large, is an expensive operation

String Literals

Now that we have introduced String slices, we can now understand that Rust does support string literals. They are just string slices pointing at strings in the code.

fn main() {
    ...
    let hello = "hello";
    println!("The value of hello is {}.", hello); // hello
}
...

This actually matches up against the immutable string type in TypeScript:

...
const hello = 'hello';
console.log(`The value of hello is: ${hello}`); // hello

The examples from this article are available for download: Rust download and TypeScript download.

Learning Rust with TypeScript: Part 5 - Structuring with structs.

Defining and Instantiating Structs

We first illustrate defining a struct (User), creating an instance of User, and creating a mutable variable pointing to it (user); pretty straightforward.

fn main() {
    let mut user = User {
        email: String::from("[email protected]"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    user.email = String::from("[email protected]");
    println!("The value of user.username is: {}.", user.username); // someusername123
}

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

One might immediately think the corresponding concept with TypeScript is a class. If, however, one things more carefully a minimal implementation simply consists of an Object.

const user = {
  email: '[email protected]',
  username: 'someusername123',
  active: true,
  signInCount: 1,
};

user.email = '[email protected]';
console.log(`The value of user.email is: ${user.username}`); // someusername123

Factory

Rust provides a very functional approach to standardizing the creation of a struct; a factory function.

fn main() {
    ...
    let user1 = build_user(
        String::from("[email protected]"),
        String::from("someusername123")
    );
    println!("The value of user1.username is: {}.", user1.username); // someusername123
}

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

Guess what, this is essentially identical to a TypeScript approach:

interface User {
  email: string;
  username: string;
  active: boolean;
  signInCount: number;
}

const buildUser = (email: string, username: string): User => ({
  email,
  username,
  active: true,
  signInCount: 1,
});
...
const user1 = buildUser('[email protected]', 'someusername123');
console.log(`The value of user.email is: ${user1.username}`); // someusername123

Struct Update Syntax

Rust provides a syntax to create structs based on other structs.

fn main() {
    ...
    let user1 = build_user(
        String::from("[email protected]"),
        String::from("someusername123")
    );
    println!("The value of user1.username is: {}.", user1.username); // someusername123

    let user2 = User {
        email: String::from("[email protected]"),
        username: String::from("anotherusername567"),
        ..user1
    };
    println!("The value of user2.username is: {}.", user2.username); // someusername123
}

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

With TypeScript, we can do the same with the Object spread syntax:

interface User {
  email: string;
  username: string;
  active: boolean;
  signInCount: number;
}

const buildUser = (email: string, username: string): User => ({
  email,
  username,
  active: true,
  signInCount: 1,
});

const user: User = {
  email: '[email protected]',
  username: 'someusername123',
  active: true,
  signInCount: 1,
};
...
const user1 = buildUser('[email protected]', 'someusername123');
console.log(`The value of user1.email is: ${user1.username}`); // someusername123

const user2: User = {...user1, email: '[email protected]', username: 'anotherusername567'};
console.log(`The value of user2.email is: ${user2.username}`); // anotherusername567

Tuple Structs

With Rust, we can type our tuples:

fn main() {
    ...
    let color = Color(0, 0, 0);
    println!("The value of the first color is: {}.", color.0); // 0
}
...
struct Color(i32, i32, i32);

More or less the same thing with TypeScript:

type Color = [number, number, number];
...
const color: Color = [0, 0, 0];
console.log(`The value of the first color is: ${color[0]}`);

Method Syntax

Rust methods are fairly straightforward too.

fn main() {
    ...
    let rect = Rectangle {
        width: 10,
        height: 10
    };
    let area = rect.area();
    println!("The value of area is: {}.", area); // 100 
    let rect1 = Rectangle {
        width: 7,
        height: 7
    };
    let rect1_fits = rect.can_hold(&rect1);
    println!("The value of rect1Fits is: {}.", rect1_fits); // true
}
...
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

With TypeScript here, we switch to using classes to implement methods.

...
class Rectangle {
  width: number;

  height: number;

  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }

  area(): number {
    return this.width * this.height;
  }

  canHold(other: Rectangle): boolean {
    return this .width > other.width && this.height > other.height;
  }
}
...
const rect = new Rectangle(10, 10);
const area = rect.area();
console.log(`The value of area is: ${area}`); // 100
const rect1 = new Rectangle(7, 7);
const rect1Fits = rect.canHold(rect1);
console.log(`The value of rect1Fits is: ${rect1Fits}`); // true

note: Interestingly enough, however, by switching to classes we loose the ability to use the Object spread syntax (used with object literals).

Associated Functions

With Rust we implement associated functions.

fn main() {
    ...
    let square = Rectangle::square(8);
    let square_area = square.area();
    println!("The value of squareArea is: {}.", square_area); //  64
}
...
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
    fn area(&self) -> u32 {
        self.width * self.height
    }
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

Guess what, this is just static methods in TypeScript:

...
class Rectangle {
  static square(size: number): Rectangle {
    return new Rectangle(size, size);
  }
  width: number;

  height: number;

  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }

  area(): number {
    return this.width * this.height;
  }

  canHold(other: Rectangle): boolean {
    return this .width > other.width && this.height > other.height;
  }
}
...
const square = Rectangle.square(8);
const squareArea = square.area();
console.log(`The value of squareArea is: ${squareArea}`); // 64

The examples from this article are available for download: Rust download and TypeScript download.

Let us walk through the examples in the Rust Book Using Structs to Structure Related Datasection and contrast them with TypeScript.

Learning Rust with TypeScript: Part 6 - Exploring the unexpected power of enums.

Enums

Strangely enough, I found enums a bit more challenging to understand than the previous material; found the idea of associating data with the enum variant hard to get my head around.

It wasn’t until the documentation described that the following enum…

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

…could be described as a list of struct-like structures.

Defining an enum with variants such as the ones in Listing 6–2 is similar to defining different kinds of struct definitions, except the enum doesn’t use the struct keyword and all the variants are grouped together under the Message type. The following structs could hold the same data that the preceding enum variants hold:

— Rust Team — Defining an Enum

struct QuitMessage; // unit struct
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct

With this in mind, we can then can make sense of enums and explore all their features:

fn main() {
    let coin = Coin::Penny;
    let coin2 = coin;
    // println!("The value of coin is: {:?}", coin); // BORROWED AFTER MOVE
    println!("The value of coin2 is: {:?}", coin2); // Penny
    coin2.display();
    let coin3 = Coin::Quarter(2);
    match coin3 {
        Coin::Penny => {
            println!("Lucky penny!");
        },
        Coin::Nickel => {
            println!("Shiny nickel!");
        },
        Coin::Dime => {
            println!("Small dime!");
        },
        Coin::Quarter(value) => {
            println!("Large quarter with value: {}", value); // 2
        },
        _ => ()
    }
    match coin3 {
        Coin::Quarter(value) => {
            println!("Large quarter with value: {}", value); // 2
        },
        _ => ()
    }
    if let Coin::Quarter(value) = coin3 {
        println!("Large quarter with value: {}", value); // 2
    } 
}

#[derive(Debug)]
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(i32),
    Euro,
}

impl Coin {
    fn display(&self) {
        println!("Displayed is: {:?}", self); // Penny
    }
}

Observations:

  • When I first wrote this example, I was surprised to see that a variable that contained an enum, e.g., coin, did not support the Copy trait. It turns out that assigning coin to coin2 is a move operation. This is completely understandable if one thinks of an enum variant as a struct-like structure
  • The second match coin3 and the if let statements are equivalent. As a matter of fact, this is the only way I could understand what if let is

While TypeScript has enums, they (for the most part) as just a convenient way to name numbers, e.g., 0, 1, 2, etc. We can mostly approximate our previous example by using a combination of an enum and a class.

enum CoinKind {
  Penny,
  Nickel,
  Dime,
  Quarter,
  Euro,
}

class Coin {
  kind: CoinKind;

  value: number;

  constructor(kind: CoinKind, value: number) {
    this.kind = kind;
    this.value = value;
  }

  display() {
    console.log(`Displayed is ${this.kind}`);
  }
}

const coin = new Coin(CoinKind.Penny, null);
const coin2 = coin;
console.log(`The value of coin is ${coin.kind}`); // 0
console.log(`The value of coin2 is ${coin2.kind}`); // 0
coin2.display(); // 0
const coin3 = new Coin(CoinKind.Quarter, 2);
switch (coin3.kind) {
  case CoinKind.Penny:
    console.log('Lucky penny!');
    break;
  case CoinKind.Nickel:
    console.log('Shiny nickel!');
    break;
  case CoinKind.Dime:
    console.log('Small dime!');
    break;
  case CoinKind.Quarter:
    console.log(`Large quarter with value: ${coin3.value}`); //2
    break;
  default:
}
if (coin3.kind === CoinKind.Quarter) {
    console.log(`Large quarter with value: ${coin3.value}`); // 2
}

Observations:

  • This approach requires that each instance have the same associated data structure (in this case the number value). Notice we had to use a null to represent a unused value
  • Unlike Rusts match statement, the switch statement does not ensure we exhaust the enum’s variants

Option Enum

In writing this article, I have come to better appreciate the following observation.

The problem with null values is that if you try to use a null value as a not-null value, you’ll get an error of some kind. Because this null or not-null property is pervasive, it’s extremely easy to make this kind of error.

— The Rust Team — [Defining an Enum](http://The problem with null values is that if you try to use a null value as a not-null value, you’ll get an error of some kind. Because this null or not-null property is pervasive, it’s extremely easy to make this kind of error.)

And come to appreciate the simplicity and power of Rust enums. Find it a bit mind-boggling that the introduction of the built-in Option enum solves the null problem.

enum Option<T> {
    Some(T),
    None,
}

Here is a simple example of using the Option enum:

fn main() {
    ...
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
}


fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

Here is the TypeScript version of the same idea:

...
const plusOne = (x: number): number => {
  if (x === null) {
    return null;
  }
  return x + 1;
};
const five = 5;
const six = plusOne(five);
const none = plusOne(null);
console.log(six); // 6
console.log(none); // null

const len = (str: string): number => {
  if (str === null) {
    return null;
  }
  return str.length;
}
const abc = 'abc';
const three = len(abc);
const nope = len(null);
console.log(three); // 3
console.log(nope); // null

Observation:

  • First observation is that neither TypeScript or strict linting (added ESLint to this example) enforced the null check in either the plusOne or len functions
  • In the case of the plusOne function, omitting the null check caused a null parameter to be treated as 0 and thus the function would return 1
  • In the case of the len function, omitting the null check caused a run-time error when passed a null parameter

The examples from this article are available for download: Rust download and TypeScript download.

Let us walk through the examples in the Rust Book Enums and Pattern Matchingsection and contrast them with TypeScript.

Learning Rust with TypeScript: Part 1 - Exploring Rust’s module system.

Packages and Crates

Going to skim through the section on Packages and Cratesas we are going to focus more on comparing the Rust and TypeScript module systems.

A Rust package delivers:

  • (0, n) Binary (executable) crates; first is src/main.rs, rest come from files in src/bin. Binary crate is built from (1, n) modules
  • (0, 1) Library crate; is src/lib.rs. Library crate can deliver (1, n) modules
  • As least one binary or library crate

A TypeScript package can deliver:

  • (0, n) Executable (interpreted by node.js) modules; values from bin value in package.json
  • (0, n) Module files; default is main value in package.json; rest from package folder hierarchy

note: Modules files can be either applications or libraries.

In our examples, we have been building a Rust package that consists of a single binary crate consisting of a single module. The TypeScript package consists of a single application module.

Modules

The remaining materials on Managing Growing Projects with Packages, Crates, and Modulesis a bit hard to understand until one also reads the Rust By Example material on Modules.

Our Rust example that is available for download consists of four modules:

+ main (file)
|
+ my_mod (folder)
    |
    + nested (file)
    |
    + private_nested

Observations:

  • The “main module” is delivered by a file src/main.rs; struggled a bit to call this a module as it only serves as the entry point for the binary (executable) crate
  • The my_mod module is delivered by a folder named the same as the module and a specially named file; src/my_mod/mod.rs
  • The nested module is delivered by a file named the same as the module; src/my_mod/nested.rs
  • The private_nested module is delivered in the same file as the my_mod module

One big difference between Rust and TypeScript modules is that Rust modules have names and TypeScript modules do not (they are just files). Related to this, a Rust file can contain multiple modules while TypeScript files cannot (as a module equals a file).

Our comparable TypeScript example that is available for download consists of three modules.

+ index (file)
|
+ myMod (folder)
    |
    + nested (file)

Observations:

  • As with the Rust example, the “index module” is odd in that it only serves as the entry point for the application; src/index.ts
  • One module is delivered by a folder, myMod, and a specially named file; src/myMod/index.ts
  • Another module is delivered by a file; src/myMod/nested.ts

Another big difference between Rust and TypeScript is that Rust modules are structured hierarchically (and when defined in files, the hierarchy is reflected in the file / folder structure) and TypeScript modules have a flat structure, i.e., folder and file naming strategies simply exists to help organize the files.

In the Rust example, we see that the nested module is a hierarchical child of my_mod module.

mod my_mod;
...
fn main() {
    function();
    my_mod::function();
    ...
    my_mod::nested::function()
    ...
}

In the TypeScript example, both myMod and nested are flatly imported.

import {
  callPublicFunctionInMyMod,
  func as myModFunc,
  indirectAccess,
  publicFunctionInCrate,
} from './myMod';
import { func as nestedFunc } from './myMod/nested';
...
func();
myModFunc();
...
nestedFunc();
...

Observations:

  • One side-effect of the flatness of TypeScript modules, is that one must manage naming collisions, e.g,. all three modules have a func member

Because Rust modules are organized into a hierarchy, Rust’s privacy rules involve it.

The way privacy works in Rust is that all items (functions, methods, structs, enums, modules, and constants) are private by default. Items in a parent module can’t use the private items inside child modules, but items in child modules can use the items in their ancestor modules.

But you can expose inner parts of child modules code to outer ancestor modules by using the pub keyword to make an item public.

— Rust Team — Paths for Referring to an Item in the Module Tree

In addition, the pub keyword can take parameters for fine grain control, e.g.:

...
pub fn function() {
    println!("called `my_mod::nested::function()`");
}
...
fn private_function() {
    println!("called `my_mod::nested::private_function()`");
}
...
// Functions declared using `pub(super)` syntax are only visible within
// the parent module
pub(super) fn public_function_in_super_mod() {
    println!("called `my_mod::nested::public_function_in_super_mod()`");
}
...

TypeScript, on the other hand, being flat only supports items being exported (public) or not (private), e.g.,

...
export const func = (): void => {
  console.log("called nested exported 'func()'");
};const privateFunction = (): void => {
  console.log("called nested private 'func()'");
};
...
// NO EQUIVALENT TO pub(super)
export const publicFunctionInSuperMod = (): void => {
  console.log("called nested exported 'publicFunctionInSuperMod()'");
};
Learning Rust with TypeScript: Part 8 - Looking at collections.

Vector (Same Type)

As Rust’s array is of fixed length, we are more likely going to use a Vector. Here we explore Vectors with items of the same type:

fn main() {
    // VECTOR (SAME TYPE)
    let mut v = vec![1, 2, 3, 4];
    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
    let one = v.get(0);
    if let Some(i) = one {
        println!("The value of the first item of v is: {}", i); // 1
    }
    for i in &v {
        println!("{}", i); // 1 2 3 4 5 6 7 8
    }
    ...
}
...

Observations:

  • Not fully sure what a macro is (will have to explore later), but the vec! macro is super handy for initializing a Vector
  • We can see the power of the Option enumerable at play here as the get method is not guaranteed to return a value

The TypeScript example is handled similarly with an array; we just need to remember to handle the situation where an indexed value could return undefined.

/* eslint-disable no-console */
// VECTOR (SAME TYPE)
const v = [1, 2, 3, 4];
v.push(5);
v.push(6);
v.push(7);
v.push(8);
const one = v[0];
if (one !== undefined) {
  console.log(`The value of the first item of v is: ${one}`); // 1
}
v.forEach(i => {
  console.log(i); // 1 2 3 4 5 6 7 8
});
...

Vector (Multiple Types)

We can use a Rust enum to handle Vectors with multiple types:

fn main() {
    ...
    // VECTOR (MULTIPLE TYPES)
    let row = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
    let three = row.get(0);
    if let Some(i) = three {
        match i {
            SpreadsheetCell::Int(value) => {
                println!("The value of the first item (i32) of row is: {}", value); // 3
            },
            SpreadsheetCell::Float(value) => {
                println!("The value of the first item (f64) of row is: {}", value);
            },
            SpreadsheetCell::Text(value) => {
                println!("The value of the first item (String) of row is: {}", value);
            },
        }
    }
    ...
}

enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

We can accomplish the same with TypeScript with TypeScript types; again we have to remember to handle the undefined case.

...
// VECTOR (MULTIPLE TYPES)
type SpreadsheetCell = number | string;
const row: SpreadsheetCell[] = [3, 'blue', 10.12];
const three = row[0];
if (three !== undefined) {
  switch (typeof three) {
    case 'number':
      console.log(`The value of the first item (number) of row is: ${three}`); // 3
      break;
    case 'string':
      console.log(`The value of the first item (string) of row is: ${three}`);
      break;
    default:
  }
}
...

Observation:

  • In this case we could typeof operator to distinguish between primitive types; in the case we were storing more complex types we could use the instanceof operator

Hash Map

We implement a Rust HashMap:

fn main() {
    ...
    // HASH MAP
    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
    let score = scores.get(&String::from("Blue"));
    if let Some(v) = score {
        println!("The value of the Blue item of scores is: {}", v); // 10
    }
    for (key, value) in &scores {
        println!("{}: {}", key, value); // Yellow: 50 Blue: 10
    }

    let teams  = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];
    let scores2: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
    let score2 = scores2.get(&String::from("Blue"));
    if let Some(v) = score2 {
        println!("The value of the Blue item of scores2 is: {}", v); // 10
    }
    for (key, value) in &scores2 {
        println!("{}: {}", key, value); // Yellow: 50 Blue: 10
    }
}

Observations:

  • Found it interesting that Rust provides default HashMap typing based on how it is used
  • As with Vectors, we can see the power of the Option enumerable at play
  • Found the line creating scores2 a bit of a mystery; my first inclination would have been to use the Iterator’s fold method

We implement the same with TypeScript:

...
// HASH MAP
const scores = new Map<string, number>();
scores.set('Blue', 10);
scores.set('Yellow', 50);
const score = scores.get('Blue');
if (score !== undefined) {
  console.log(`The value of the Blue item of score is: ${score}`);
}
scores.forEach((value, key) => {
  console.log(`${key}: ${value}`); // Blue: 10 Yellow: 50
});

const teams = ['Blue', 'Yellow'];
const initialScores = [10, 50];
const scores2 = new Map<string, number>();
teams.reduce((accumulator, currentValue, i) => {
  accumulator.set(currentValue, initialScores[i]);
  return accumulator;
}, scores2);
const score2 = scores2.get('Blue');
if (score2 !== undefined) {
  console.log(`The value of the Blue item of score2 is: ${score2}`);
}
scores2.forEach((value, key) => {
  console.log(`${key}: ${value}`); // Blue: 10 Yellow: 50
});

Observations:

  • While we could have likely used a regular TypeScript object (with some fancy typing), there are reasons to use a Map instead
  • As with arrays, we need to be sure to check for undefined values
  • Here we used the reduce method (much like Rust’s fold)

The examples from this article are available for download: Rust download and TypeScript download.

Let us walk through the examples in the Rust Book Common Collections section and contrast them with TypeScript.

Learning Rust with TypeScript: Part 9 - Error handling and generic data types.

Error Handling

One thing that is special about Rust is that it forces you to handle errors.

Rust’s commitment to reliability extends to error handling. Errors are a fact of life in software, so Rust has a number of features for handling situations in which something goes wrong. In many cases, Rust requires you to acknowledge the possibility of an error and take some action before your code will compile. This requirement makes your program more robust by ensuring that you’ll discover errors and handle them appropriately before you’ve deployed your code to production!

— Rust Team — Error Handling

It is also surprising how Rust’s enum structure is used to solve a wide variety of problems; including error handling with the Result enum.

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

Here we implement the example, from the Error Handling section, that opens or creates a file:

use std::io;
use std::fs::File;
use std::io::ErrorKind;

const FILE_NAME: &str = "hello.txt";

fn main() {
    let f = open_or_create_file();
}

fn open_or_create_file() -> Result<File, io::Error> {
    let f = File::open(FILE_NAME); 
    match f {
        Ok(file) => Ok(file),
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create(FILE_NAME) {
                Ok(fc) => Ok(fc),
                Err(e) => Err(e)
            },
            _ => Err(error),
        },
    }
}

We implement this same functionality in TypeScript:

import { openSync, writeFileSync } from 'fs';

const ENOENT = 'ENOENT';
const FILE_NAME = './hello.txt';

const openOrCreateFile = (): number => {
  try {
    return openSync(FILE_NAME, 'r');
  } catch (err) {
    if (err.code === ENOENT) {
      writeFileSync(FILE_NAME, '');
      return openSync(FILE_NAME, 'r');
    }
    throw err;
  }
};

const fd = openOrCreateFile();

Observations:

  • First, TypeScript, unlike Rust, does not require us to handle the errors; we have to remember to handle them
  • Unlike Rust with the more general approach using enums, TypeScript has a special syntax, try catch, used specifically for error handling

Generic Data Types

Let us walk through the examples in the Rust Book Generic Data Types section and contrast them with TypeScript.

The code for this section is available to download; Rust download and TypeScript download.

Rust’s generic data types, for the most part, is pretty straightforward; assuming you have used them in some other language.

fn main() {
    // IN FUNCTION
    let x = identity(0); // i32
    let y = identity(3.0); // f64

    // IN STRUCT
    let integer = Point { x: 5, y: 10 }; // Point<i32>
    let float = Point { x: 1.0, y: 4.0 }; // Point<f64>

    // IN METHOD
    let integer_x = integer.x(); // &i32
    let float_x = float.x(); // &f64

    // IN METHOD SPECIFIC TYPE
    let d = float.distance_from_origin(); // f64
}

fn identity<T>(item: T) -> T {
    item
}

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

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

We implement most of the same feature in TypeScript:

const identity = <T>(item: T): T => item;

interface Point<T> {
  x: T;
  y: T;
}

const pointToX = <T>(point: Point<T>): T => point.x;

class PointClass<T> {
  x: T;

  y: T;

  constructor(x: T, y: T) {
    this.x = x;
    this.y = y;
  }

  getX(): T {
    return this.x;
  }
}

// IN FUNCTION
const x = identity(0); // number
const y = identity('hello'); // string

// IN STRUCT
const integer = { x: 5, y: 10 };
const str = { x: 'hello', y: 'world' };
const integerX = pointToX(integer); // number
const strX = pointToX(str); // string

// IN METHOD
const integerClass = new PointClass(5, 10);
const strClass = new PointClass('hello', 'world');
const integerClassX = integerClass.getX(); // number
const strClassX = strClass.getX(); // string

// IN METHOD SPECIFIC TYPE
// NO IDEA HOW TO IMPLMENT IN TYPESCRIPT

Observations:

  • Could not think how to cleanly implement the equivalent of the type-specific method, distance_from_origin, in TypeScript. The closest that I think we could come would be to create a method that would throw a run-time error if used with an incorrect type (this felt wrong)

Let us walk through the examples in the Rust Book Error Handlingsection and contrast them with TypeScript.

The code for this section is available to download; Rust download and TypeScript download.

Next Steps

Going to take a break for a week or so; my head is full…

Learning Rust by Contrasting with TypeScript

Learning Rust by Contrasting with TypeScript

Learning Rust by Contrasting with TypeScript. Let us walk through the examples in the Rust Book Common Collections section and contrast them with TypeScript. As Rust's array is of fixed

Prerequisites

The common requirement for both the Rust and TypeScript projects is the GIT version control system.

Rust Installation

Rust installation involves installing global binaries, e.g., rustc and cargo. This article was written using version 1.38.0.

TypeScript Installation

TypeScript depends on the JavaScript runtime Node.js; must first be installed. This installation involves installing global binaries, e.g., node and npm. This article was written using version 12.13.0.

TypeScript itself is installed as a Node.js project dependency as we will see below.

Rust Project Creation

Project setup involves running the cargo command to scaffold a Rust project:

note: The completed project is available for download.

cargo new r00_hello_world

The project is also initialized as a GIT repository with a .gitignore file. It also includes the sample source file: src/main.rs:

main.rs

fn main() {
    println!("Hello, world!");
}

TypeScript Project Creation

Project setup first requires scaffolding a JavaScript Node.js project by creating a folder and using the npm command:

note: The completed project is available for download.

mkdir t00_hello_world
cd t00_hello_world
npm init --yes

We initialize the project as a GIT repository with:

git init

and create a .gitignore file:
.gitignore

node_modules

We then convert the JavaScript Node.js project into TypeScript by first installing the TypeScript development dependency:

npm install -D typescript

We then create a TypeScript configuration file: tsconfig.json. Here we use the microsoft/TypeScript-Node-Starter example.

We update the .gitignore file to ignore the output directory: dist:

.gitignore

node_modules
dist

We update the package.json; updating the main value and adding a start and build to scripts:

package.json

{
  "name": "t00_hello_world",
  "version": "1.0.0",
  "description": "",
  "main": "dist/index.js",
  "scripts": {
    "start": "node dist/index.js",
    "build": "tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^3.6.4"
  }
}

Finally, we create the source file: src/index.ts:

index.ts

console.log('Hello World!');

Rust Build

During development, we use the following command to build a Rust project:

cargo build

The output is a compiled OS executable: target/debug/r00_hello_world.

For release, an optimized build, we use the following command:

cargo build --release

The output is a compiled OS executable: target/release/r00_hello_world.

TypeScript Build

We use the following command to build a TypeScript project:

npm run build

The output is a transpiled JavaScript file: dist/index.js.

Rust Execute

We simply execute the compiled OS executable as we would any other:

./target/debug/r00_hello_world

We can also use the command:

cargo run

to both build and run.

TypeScript Execute

We execute the transpiled JavaScript file using the npm script:

npm start

or we can execute it directly:

node dist/index.js

In either case, we are executing our project on top of the JavaScript runtime.

Variables and Mutability

Let us walk through the examples in the Rust Book Variables and Mutability section and contrast them with TypeScript.

With Rust, a variable set with let as shown is immutable.

main.rs

...
fn main() {
    // IMMUTABLE
    let x = 5;
    println!("The value of x is: {}", x); // 5
    // x = 6; // CANNOT ASSIGN TWICE TO IMMUTABLE VARIABLE 
    // println!("The value of x is: {}", x);
    ...
}

In TypeScript, the equivalent is a variable set with const.

index.ts

...
// IMMUTABLE
const x = 5;
console.log(`The value of x is: ${x}`); // 5
// x = 6; // CANNOT ASSIGN BECAUSE CONSTANT
// console.log(`The value of x is: ${x}`)
...

note: In both Rust and TypeScript the variable types are inferred from the right-hand-side; thus explicit typing is not required.

With Rust, a variable set with let mut as shown is mutable.

main.rs

...
fn main() {
    ...
    // MUTABLE
    let mut y = 5;
    println!("The value of y is: {}", y); // 5
    y = 6;
    println!("The value of y is: {}", y); // 6
    ...
}

In TypeScript, the equivalent is a variable set with let.

index.ts

...
// MUTABLE
let y = 5;
console.log(`The value of y is: ${y}`); // 5
y = 6;
console.log(`The value of y is: ${y}`); // 6
...

With Rust, a variable set with let and let mut can be set at run-time:

main.rs

fn another_function(x: i32) -> i32 {
    return x + 1;
}

fn main() {
    ...
    // RUN-TIME ASSIGNMENT
    let z = another_function(5);
    println!("The value of z is: {}", z); // 6
    let mut zz = another_function(5);
    zz = zz + 1;
    println!("The value of zz is: {}", zz); // 7
    ...
}

Likewise for TypeScript for variables set with both const and let:
index.ts

const anotherFunction = (x: number): number => x + 1;
...
// RUN-TIME ASSIGNMENT
const z = anotherFunction(5);
console.log(`The value of z is: ${z}`); // 6
let zz = anotherFunction(5);
zz = zz + 1;
console.log(`The value of zz is: ${zz}`); // 7
...

Rust has another immutable variable type; const. Unlike, let however, const cannot be set at run-time (only set at compile-time). The upper-case const variable names is by convention.
main.rs

n another_function(x: i32) -> i32 {
    return x + 1;
}

fn main() {
    ...
    // CONSTANT
    const MAX_POINTS: i32 = 100000;
    println!("The value of MAX_POINTS is: {}", MAX_POINTS); // 100000
    const ANOTHER_CONSTANT: i32 = 100000 + 1; // COMPILE-TIME ASSIGNMENT
    println!("The value of ANOTHER_CONSTANT is: {}", ANOTHER_CONSTANT); // 100001 
    // const YET_ANOTHER_CONSTANT: i32 = another_function(1); // CANNOT RUN-TIME ASSIGNMENT
    // println!("The value of YET_ANOTHER_CONSTANT is: {}", YET_ANOTHER_CONSTANT);
    ...
}

TypeScript does not have an equivalent variable type. For this situation, one typically uses the TypeScript const variable type and simply name it with upper-case by convention.

Rust allows one to redefine, or shadow, a variable of type let:

main.rs

...
fn main() {
    ...
    // SHADOWING
    let a = 5;
    let a = a + 1;
    let a = a * 2;
    println!("The value of a is: {}", a); // 12
}

note: It is important to observe that the resultant variable a can still be immutable. Also, while not illustrated in this example, the variable types can also differ through shadowing.

TypeScript does not have an equivalent feature, i.e., TypeScript cannot redeclare a block-scoped variable. In TypeScript, one often simply creates a new const variable name in this situation.

Data Types

Now we walk through the examples in the Rust Book Data Typessection; starting first with the Rust scalar types.

main.rs

...
fn main() {
    ...
    // NUMBERS
    let i = 1; // i32
    println!("The value of i is: {}", i); // 1
    let j = 1.1; // f64
    println!("The value of j is: {}", j); // 1.1

    // BOOLEAN
    let b = true; // bool
    println!("The value of b is: {}", b); // true

    // CHARACTER
    let c = 'a'; // char
    println!("The value of c is: {}", c); // a
    ...
}

Let us consider the equivalents (called primitives) in TypeScript:
index.ts

...
// NUMBERS
const i = 1; // number
console.log(`The value of i is: ${i}`); // 1
const j = 1.1 // number
console.log(`The value of j is: ${j}`); // 1.1

// BOOLEAN
const b = true; // boolean
console.log(`The value of b is: ${b}`); // true

// CHARACTER
const c = 'a'; // string
console.log(`The value of c is: ${c}`); // a
...

One big difference is that in JavaScript (and thus TypeScript) all numbers are 64-bit floating point where-as Rust there are a number of integer and floating point types.

Another difference is that JavaScript (and thus TypeScript) has a primitive string type that can contain an arbitrary number of characters; not just a single character.

Rust has two compound types; tuple and array. A tuple being a fixed length ordered list of values of varying types. An array being a fixed length ordered list of values of the same type.

main.rs

...
fn main() {
    ...
    // TUPLE
    let tup = (0, 'a', 1.1); // (i32, char, f64)
    println!("The second value of tup is: {}", tup.1); // a
    let (t1, t2, t3) = tup;
    println!("The value of t1 is: {}", t1); // 0
    println!("The value of t2 is: {}", t2); // a
    println!("The value of t3 is: {}", t3); // 1.1

    // ARRAY
    let arr = [0, 1, 2]; // [i32, 3]
    println!("The second value of arr is: {}", arr[1]); // 1
    let [a1, a2, a3] = arr;
    println!("The value of a1 is: {}", a1); // 0
    println!("The value of a2 is: {}", a2); // 0
    println!("The value of a3 is: {}", a3); // 0
    ...
}

In TypeScript, a tuple is essentially the same as in Rust. An array, however is of arbitrary length, e.g., we can append a value.

index.ts

...
// TUPLE
const tup: [number, string, number] = [0, 'a', 1.1]; // [number, string, number]
console.log(`The second value of tup is: ${tup[1]}`); // a
const [t1, t2, t3] = tup;
console.log(`The value of t1 is: ${t1}`); // 0 
console.log(`The value of t2 is: ${t2}`); // a
console.log(`The value of t3 is: ${t3}`); // 1.1

// ARRAY
const arr = [0, 1, 2]; // number[]
console.log(`The second value of arr is: ${arr[1]}`); // 1
const [a1, a2, a3] = arr;
console.log(`The value of a1 is: ${a1}`); // 0 
console.log(`The value of a2 is: ${a2}`); // 1
console.log(`The value of a3 is: ${a3}`); // 2
arr[3] = 3;
console.log(`The fourth value of arr is: ${arr[3]}`); // 3
...

Sidebar into Object or Reference

So far, Rust and TypeScript have been relatively similar; however there is a big difference lurking in compound types. The difference is in how each of them store compound types in variables.

In Rust, the value of a compound type variable is the object itself.

main.rs

...
fn main() {
    ...
    // OBJECT OR REFEREENCE
    let mut tup2 = (0, 'a', 1.1); // (i32, char, f64)
    let tup3 = tup2;
    tup2.0 = 1;
    println!("The first value of tup2 is: {}", tup2.0); // 1
    println!("The first value of tup3 is: {}", tup3.0); // 0

    let mut arr2 = [0, 1, 2]; // [i32, 3]
    let arr3 = arr2;
    arr2[0] = 1;
    println!("The first value of arr2 is: {}", arr2[0]); // 1
    println!("The first value of arr3 is: {}", arr3[0]); // 0
}

Observations:

  • We have to use let mut for tup2 in order to mutate the value later
  • Assigning tup2 to tup3 (copying their value) creates a completely new tuple
  • The result is that mutating tup2 has no relevance to tup3
  • Some logic applies to arrays

On the other hand, in TypeScript, the value of a compound type is a reference to the object.

note: In TypeScript, primitive types (boolean, number, string) behave like Rust with the value of the variable being the object itself.

index.ts

...
// OBJECT OR REFERENCE
const tup2: [number, string, number] = [0, 'a', 1.1]; // [number, string, number]
const tup3 = tup2;
tup2[0] = 1;
console.log(`The first value of tup2 is ${tup2[0]}`); // 1
console.log(`The first value of tup3 is ${tup3[0]}`); // 1

const arr2 = [0, 1, 2]; // number[]
const arr3 = arr2;
arr2[0] = 1;
console.log(`The first value of arr2 is ${arr2[0]}`); // 1
console.log(`The first value of arr3 is ${arr3[0]}`); // 1
...

Observations:

  • We can use const for tup2 because we are mutating the object and not the reference
  • Assigning tup2 to tup3 (copying their value) copies the reference to the same tuple object
  • The result that mutating tup2 is really mutating the common tuple object referenced by both tup2 and tup3
  • Same logic applies to arrays

I can already see that this is going to take me some getting used to as I have the TypeScript pattern etched into my brain.

Addendum 11/8/19: Having written a couple of more articles, I have come to realize (believe it was buried in the Rust book too), that the compound types tuple and array are likely only to be used as constants, e.g., days of the week, and as such this difference between Rust and TypeScript is irrelevant. Specifically, we will likely be using the Rust Vec type as the equivalent to TypeScript arrays.

Control Flow

The core concepts around flow of control are virtually identical between Rust and TypeScript; confirming this by walking through examples in the Rust Book Control Flowsection.

fn another_function() {
    println!("Another function.");
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

fn abs(x: i32) -> i32 {
    if x >= 0 {
        return x;
    }
    x * -1
}

fn main() {
    // FUNCTIONS
    another_function();
    let x = 0;
    let y = plus_one(x);
    println!("The value of y is: {}", y); // 1
    let a = -1;
    let b = abs(a);
    println!("The value of b is: {}", b); // 1

    // COMMENTS
    /*
    Rust supports
    mult-line comments.
    */

    // IF EXPRESSIONS
    let number = 3;
    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }

    let number = 6;
    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }

    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };
    println!("The value of number is: {}", number);

    // LOOPS
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2;
        }
    };
    println!("The result is {}", result);

    let mut number = 3;
    while number != 0 {
        println!("{}!", number);
        number -= 1;
    }
    println!("LIFTOFF!!!");

    let a = [10, 20, 30, 40, 50];
    for element in a.iter() {
        println!("the value is: {}", element);
    }
}

The same implemented in TypeScript:
index.ts

const anotherFunction = () => console.log('Another funciton.');

const plusOne = (x: number): number => x + 1;

const abs = (x: number): number => {
  if (x >= 0) {
    return x;
  }
  return x * -1;
}

// FUNCTIONS
anotherFunction();
const x = 0;
const y = plusOne(x);
console.log(`The value of y is: ${y}`); // 1
const a = -1;
const b = abs(a);
console.log(`The value of b is: ${b}`); // 1

// COMMENTS
/*
TypeScript supports
multi-line comments.
*/

// IF EXPRESSIONS
const number = 3;
if (number < 5) {
  console.log('condition was true');
} else {
  console.log('condition was false');
}

const number2 = 6;
if (number2 % 4 === 0) {
  console.log('number2 is divisible by 4');
} else if (number2 % 3 === 0) {
  console.log('number2 is divisible by 3');
} else if (number2 % 2 === 0) {
  console.log('number2 is divisible by 2');
}

const condition = true;
const number3 = condition ? 5 : 6;
console.log(`The value of number3 is: ${number3}`);

let counter = 0;
let result: number;
while (true) {
  counter += 1;
  if (counter === 10) {
    result = counter * 2;
    break;
  }
}
console.log(`The result is ${result}`);

let number4 = 3;
while (number4 !== 0) {
  console.log(`${number4}!`);
  number4 -= 1;
}
console.log('LIFTOFF!!!');

const arr = [10, 20, 30, 40, 50];
arr.forEach(element => console.log(`the value is: ${element}`));

Observations:

  • The Rust pattern of having the last line of a function being an expression (with no semi-colon) to mean the return value was novel to me
  • Having gotten used to the TypeScript ternary operator, found the Rust equivalent a bit verbose
  • With Rust, having a loop return a value is novel to me

Next Steps

We dive into the complexities of ownership in the next article: Learning Rust by Contrasting with TypeScript: Part 3.