Exploring Scala CLI — the new scala runner

Piotr Chabelski
VirtusLab
Published in
7 min readFeb 22, 2023

Some of you may have heard that as of SIP-46, Scala CLI is to become the new default runner that will replace the old scala script. Upon receiving this information, many questions arise. Namely, what is Scala CLI and what is it good for? What does this change mean for the Scala community? Or, as many everyday Scala users could probably ask — what even is the scala runner?

I will try to answer these questions in the following runthrough of the situation.

Please note that for the reader’s convenience, I will still differentiate the old runner as scala and the new runner (Scala CLI) as scala-cli for the purpose of giving examples in this post, unless otherwise stated. Please also note that the new runner is already available as scala under the scala-experimental installation at the time of writing this post.

What is the scala runner?

Since you are reading this, it is safe to assume you use Scala in some capacity in your life, be it for your job, university, pet project or any other environment. That means you probably also have the language installed on your local machine.

The average Scala installation gives you access to the following command-line applications:

  • scalac — the app used to interact with the Scala compiler
  • scalap — the Scala class file decoder
  • scaladoc — the command-line utility for generating Scaladoc
  • scala — the runner we’re discussing in this article

As the name implies, the scala runner is a command-line app which runs Scala code.

Given a simple HelloWorld.scala example:

object Hello extends App {
println("Hello world")
}

Using the runner, you could run the file straight from the command line like this:

scala HelloWorld.scala
# Hello world

Another commonly used feature would be entering the repl:

scala
# Welcome to Scala 3.2.1 (17.0.5, Java OpenJDK 64-Bit Server VM).
# Type in expressions for evaluation. Or try :help.
#
# scala>

Why do we need a new runner?

Until now, the scala runner used to be primarily a power user tool. Its full scope of usefulness was not widely known. Its features have been relatively limited, not offering much beyond launching the repl and running .scala files. To do anything non-basic with Scala, you would immediately have to turn to a build tool, like SBT or Mill. And even when it comes to launching the REPL, most Scala coders have been using it from SBT, skipping the runner altogether. As a result, the utility has gradually been getting less and less attention. It seems like an unused opportunity since we are talking about an app installed along with the compiler on many Scala coders’ machines.

Scala CLI is meant to be a game changer, being the runner “with batteries included”, targeting not just power users but the whole Scala community.

Scala CLI showcase

Scala CLI is a relatively new open-source command-line tool from VirtusLab, developed since mid-2021. Its core use cases include prototyping, education, scripting and single-module projects. It allows users to not just run their Scala code from the command line but also compile, package and much more.

Scala CLI is not meant to be a replacement for full-fledged build tools (like Mill or SBT), but is perfectly sufficient to act as one for simple projects consisting of a single module.

Beyond the JVM platform, the tool also plays nicely with Scala Native and Scala.js.

Its features are divided into sub-commands. Here is a list of some of the more important ones:

  • run covers the runner functionality
  • test enables running tests
  • compile compiles the given inputs
  • package allows the user to create a jar, a native binary or even a docker image
  • fmt allows the user to format code, running scalafmt under the hood
  • doc enables the generation of API documentation

More features are planned for the future, with things like publishing, Python, Spark and Markdown support being in the experimental phase.

There’s also IDE support for IDEA IntelliJ and VS Code with Metals.

Advantages Scala CLI has over the old scala runner

Now, that’s all nice and good, but when it comes down to the actual runner functionalities, how is Scala CLI better than its predecessor?

Simpler setup

First, the setup is a lot easier. And I don’t just mean the language setup but the whole development environment. Normally, you would have to separately install the appropriate Java version, scalafmt (a Scala formatter used in a majority of projects) and other things, potentially including a build tool. Scala CLI does all that for the user.

Want to work with a particular Scala version other than the one available by default on your PATH? Nothing simpler. You can change it just by specifying it with an option.

scala-cli -S 2.13
# Welcome to Scala 2.13.10 (OpenJDK 64-Bit Server VM, Java 17.0.5).
# Type in expressions for evaluation. Or try :help.
#
# scala>

Similarly, picking a Java version doesn’t require downloading and installing it separately. The tool handles that as well.

scala-cli --jvm 15
# Downloading JVM zulu:15
# Welcome to Scala 3.2.1 (15.0.9, Java OpenJDK 64-Bit Server VM).
# Type in expressions for evaluation. Or try :help.
# scala>

In fact, there is no need to install Java separately at all. It’s quite okay to let Scala CLI do it.

scalafmt is built in as well, so no need to worry about that.

scala-cli fmt .

Providing a .scalafmt.conf file (configuration for scalafmt) isn’t necessary, either. If it’s not present, Scala CLI will assume default settings.

Separately installing a build tool is also optional unless you’re planning to build a multi-module project. And even then, nothing’s stopping you from using Scala CLI during the prototyping phase and then migrating once the need arises. The export sub-command makes it relatively easy to convert a Scala CLI project to Mill or SBT.

Faster compilation times

Scala CLI uses Bloop under the hood, which makes compilation times a lot faster.

It’s also one of the easier ways to interact with Bloop. So using Scala CLI’s compile sub-command may be favourable over calling scalac directly if you care about getting something compiled quickly.

Introduction of using directives

Scala CLI introduces the concept of using directives, which allows for defining configuration information in sources.

This means that things like dependencies or Scala version can be defined straight up in a Scala file without the need for a separate configuration file.

As an example, to define a simple pwd.sc script using Scala 3.2.1 and os-lib 0.9.0, you have to type:

//> using scala "3.2.1"
//> using lib "com.lihaoyi::os-lib:0.9.0
println(os.pwd)
scala-cli pwd.sc
# Compiling project (Scala 3.2.1, JVM)
# Compiled project (Scala 3.2.1, JVM)
# ~/scala-cli-tests/demo

This is particularly useful for replicating and reporting bugs, as the exact configuration and code used in a given build can be put in a single code block. Defining dependencies right in the source, where they are immediately used, speeds up prototyping as well.

Additionally, using directives can be used in any input accepted by Scala CLI, which enables applying it in creative ways. For example, nothing stops you from using them in a .java source:

//> using jvm "16"
public class Main {
public static void main(String[] args) {
System.out.println(System.getProperty("java.version"));
}
}
scala-cli Main.java
# Compiling project (Java)
# Compiled project (Java)
# 16.0.2

Support for virtual sources

Scala CLI has support for virtual sources, which means the input code does not necessarily have to be on your file system.

For example, it’s possible to run a GitHub Gist:

scala-cli https://gist.github.com/Gedochao/9816a2d3ca2597a77dcf8a3d9bc398a2
# Compiling project (Scala 3.2.1, JVM)
# Compiled project (Scala 3.2.1, JVM)
# Hello

Or simply a URL:

scala-cli https://gist.githubusercontent.com/Gedochao/9577c5c7b06cd655b80c5da93a2bf5d3/raw/4600ff9733678c2d36e864384c4cc45188459369/hello.sc
# Compiling project (Scala 3.2.1, JVM)
# Compiled project (Scala 3.2.1, JVM)
# Hello

However, please note that Scala CLI does not provide any sandboxing at the time of writing this article, so make sure that you trust any remote sources you decide to run.

There are also other options, like piping sources or process substitution.

IDE support through BSP

Scala CLI has official support for IDEs through BSP (Build Server Protocol), working with IDEA IntelliJ and Metals (with Visual Studio Code or other editors supported by Metals). This allows you to use the runner from the comfort of an IDE’s graphical interface instead of having to use the command line for everything.

The new runner’s advanced features

Some of the features delivered by Scala CLI were deemed a bit advanced for the average target user of the scala runner but are nonetheless available behind the --power launcher option.

A good example of such a power user feature is the package sub-command. It enables packaging code in various formats, like JARs, docker containers or native images.

For example, the following command packages a project to a GraalVM native image:

scala-cli package Main.scala -o hello --native-image
./hello
# Hello

When installing Scala CLI as scala, you have to pass the --power flag as the first argument to the app (before even a sub-command).

And so, to achieve the same result with package, producing a GraalVM native image requires the following:

scala --power package Main.scala -o hello --native-image
./hello
# Hello

It’s also possible to enable --power globally, allowing the new runner to always expose all of its features.

scala config power true

Testing the new scala runner early

As mentioned earlier, even though SIP-46 is still in its implementation phase, you can try the new runner early. You can do so by using the scala-experimental installation.

Currently, only two installation methods are supported, with more to come later.

coursier

cs setup
cs install scala-experimental

brew

brew install virtuslab/scala-experimental/scala

Alternatively, you can also install Scala CLI as scala-cli with one of its official installation methods.

Try it out and report any issues to the Scala CLI repository!

--

--