Learning Rust by Contrasting with TypeScript: Part 12

John Tucker
codeburst
Published in
3 min readNov 25, 2019

--

Exploring Rust closures.

This article is part of a series starting with Learning Rust by Contrasting with TypeScript: Part 1.

Let us walk through the examples in the Rust Book Functional Language Features: Iterators and Closures section and contrast them with TypeScript.

The code for this section is available to download in two parts; Rust download part 1 and part 2 and TypeScript download part 1 and part 2.

Creating an Abstraction of Behavior with Closures
Closure Type Inference and Annotation
Storing Closures Using Generic Parameters and the Fn Traits
Limitations of the Cacher Implementation

The first part of the section, Closures: Anonymous Functions that Can Capture Their Environment, goes through building an elaborate example of demonstrating Rust closures as anonymous functions. The confusing thing here, given the title of the section, is that this example does not use any features that are closure-like (from a TypeScript perspective).

The final solution is src/main.rs.

In going through the example, another confusing thing was that it felt odd to need to use the generic T as shown below.

My first inclination was to do something like the following:

Turns out, the key here is that Fn is a trait and as such needs to be treated as such.

The TypeScript example, src/index.ts, is essentially the same; just swapping out closures for arrow functions, structs for classes and Options for null handling.

One key difference, however, between Rust and TypeScript is that in TypeScript, we can also pass functions (in addition to arrow function) as parameters; not so in Rust.

Capturing the Environment with Closures

With Rust, closures (not functions) can also use (also referred to as capturing) variables from enclosing scopes in much the same way variables are used as parameters when calling functions, e.g., copying, moving, borrowing, etc.

The key difference between captured variables and parameters, however, is that variables are captured at closure creation time while parameters are passed at closure execution time.

In the following example, x is captured by the equal_to_x closure as a copy (just like an i32 would be copied into the function’s execution scope as a parameter).

Much like Rust closures, TypeScript functions capture variables from enclosing scopes at function creation time; stored the function’s closure.

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

— MDN — Closures

Let us examine TypeScript closures in a TypeScript example based on the previous Rust example.

Observations:

  • Here both the named function, equalToXF, and the arrow function, equalToX, both capture the variable x into a closure (copies the value) when they are created

Processing a Series of Items with Iterators

The next section, Processing a Series of Items with Iterators, explores the use of closures in the context of iterating through a Vec (vector).

In TypeScript, the solution is very similar:

Observations:

  • One key difference between Rust and TypeScript is that with Rust, one must first (to iterate) obtain a mutable Iterator for the Vec. In TypeScript, the Array object directly has the methods for iteration
  • Also, it is important to note that in Rust, when we call map we return a new Iterator; the closure is not actually executed. In TypeScript, map executes the arrow function and returns a new Array

Creating Our Own Iterators with the Iterator Trait

Rust has first-class support for implementing custom implementations of the Iterator trait.

Interestingly enough, TypeScript has a comparable Iterator feature. But…

  • While the JavaScript (and thus TypeScript) Iterator was introduced in ES2015, it is not widely understood or used
  • When we try to use the Iterator feature with arrays, our ESLint rules warn us “iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.”

Next Steps

Continue learning about smart pointers in Learning Rust by Contrasting with TypeScript: Part 13.

--

--