Skip to main content

Scala Scripts with instant startup

scala-cli allows to easly compile and run Scala Scripts. It also allows for straightforward compilation with Scala Native. Scala Native is an ahead-of-time compiler to native binary allowing for instant startup times, meaning that along with scala-cli, it should perfectly suit the needs of a fast scripting tool.

Using Scala Native

As an example, let’s build a script printing files from a directory with sizes bigger than a passed value.

size-higher-than.scala
//> using scala "3.1.1"
//> using lib "com.lihaoyi::os-lib::0.8.1"

@main
def sizeHigherThan(dir: String, minSizeMB: Int) =
val wd = os.pwd / dir
val files = os.walk.attrs(wd).collect{
case (p, attrs) if attrs.size > minSizeMB * 10E6 => p
}
files.foreach(println(_))

Running this for a dir directory and 20 MB as a lower limit with scala-cli size-higher-than.scala – dir 20 can give us for example:

scala-cli size-higher-than.scala -- dir 20
Compiling project (Scala 3.1.1, JVM)
Compiled project (Scala 3.1.1, JVM)
/Users/user/Documents/workspace/dir/large-file.txt

A keen eye will notice that we have not yet compiled to Scala Native. We are still running on the JVM! We can fix that by either running with a —-native option, or, in this case, by including an additional using directive:

size-higher-than.scala
//> using scala "3.1.1"
//> using lib "com.lihaoyi::os-lib::0.8.1"
//> using platform "scala-native"

@main
def sizeHigherThan(dir: String, minSizeMB: Int) =
val wd = os.pwd / dir
val files = os.walk.attrs(wd).collect{
case (p, attrs) if attrs.size > minSizeMB * 10E6 => p
}
files.foreach(println(_))

After rerunning, you may notice that while the initial compilation took a little longer, subsequent runs will severely cut on the startup time compared to the JVM.

Optimization options

We can make the runtime itself even faster, using various Scala Native optimization options:

  • debug - what was used by default up to this point, fast compilation with a slower runtime
  • release-fast - moderate compilation time with a faster runtime
  • release-full - slow compilation time with the fastest runtime

We pass these using a -–native-mode scala-cli option or, like previously, by adding a using directive:

size-higher-than.scala
//> using scala "3.1.1"
//> using lib "com.lihaoyi::os-lib::0.8.1"
//> using platform "scala-native"
//> using nativeMode "release-full"

@main
def sizeHigherThan(dir: String, minSizeMB: Int) =
val wd = os.pwd / dir
val files = os.walk.attrs(wd).collect{
case (p, attrs) if attrs.size > minSizeMB * 10E6 => p
}
files.foreach(println(_))

We can also package this script into a separate binary with the package command, useful especially on Windows where typically shebangs won’t work:

Additional considerations

Some things to look out for when working with Scala Native:

  • dependencies - libraries have to be published separately for Scala Native. Notice the org::project::version double colon syntax used for os-lib - it basically hides org::project_native[Scala Native binary version]:version underneath. Fortunately, many libraries are already available for Scala Native. However, Java dependencies will not work altogether.
  • some differences exist when compared to Scala on the JVM.