1. 序章

Scalaは、JVM上に構築された言語の1つです。 Scalaコンパイラーは、ScalaファイルをJVMバイトコードにコンパイルします。 その結果、ScalaはScalaコードベースで直接Javaライブラリを使用できるようになります。 これは、Scalaが初期段階でより多くの注目を集めるのに役立ちました。

ただし、 JVMは起動が遅いことで有名であり、一般にメモリフットプリントも大きくなります。 その結果、Scalaコミュニティは Scala Nativeを積極的に開発しています。これは、JVMを必要とせずにネイティブScalaアプリケーションを作成する方法です。

このチュートリアルでは、ScalaNativeの利点を見ていきます。 次に、それを使用して単純なネイティブアプリケーションを構築します。

2. Scala Nativeとは何ですか?

Scala Nativeは、最適化事前(AOT)コンパイラーであり、Scala用の軽量ランタイムです。 Scalaネイティブコンパイラプラグインは、Scalaコードを直接機械語に変換し、JVMがアプリケーションを実行する必要をなくします。

3. Scalaネイティブの利点

ScalaNativeを使用する利点のいくつかは次のとおりです。

  • 起動時間の短縮とメモリフットプリントの削減
  • Scalaのほぼすべての機能を利用してネイティブアプリケーションを作成できます
  • 低レベルのプリミティブを使用して最適化されたプログラムを作成するためのサポート
  • Cで記述されたネイティブコードとの相互運用性
  • コマンドラインアプリケーション、組み込みアプリ、サーバーレスラムダなどのアプリケーションの構築に役立ちます

4. Scalaネイティブの概要

4.1. コンパイルフロー

Scala Nativeコンパイラーは、Scalaファイルを Native Intermediate Representation(NIR)と呼ばれる中間形式に変換します。 NIRは、リンクヒントやインライン化などの追加の低レベル情報を含むTASTy/クラスファイルのより充実した形式です。 次に、このNIRファイルは LLVMIRファイルに変換されます。これはさらに別の中間形式です。 最後のステップで、LLVM静的コンパイラーはIRファイルをマシン依存のアセンブリ形式に変換します。

4.2. 言語意味論

Scalaネイティブアプリケーションでは、ほとんどのScala機能を使用できます。 ただし、マルチスレッドはまだサポートされていません。 また、 Scala Nativeチームは、Oracle のライセンスの問題のため、Java標準ライブラリの実装を書き直す必要がありました。 それでも、同じ参照仕様に従っているため、標準のJavaライブラリとの不一致が発生することはありません。

5. シンプルなネイティブアプリの作成

5.1. 依存関係

開発時には、ScalaNativeはいくつかのツールに依存しています。

5.2. インストール

他のScalaプロジェクトと同じようにSBTプラグインを追加することで、Scalaネイティブアプリケーションを作成できます。

addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.4")

次に、build.sbtファイルで上記のプラグインを有効にしましょう。

enablePlugins(ScalaNativePlugin)

さらに、アプリケーションに必要なライブラリの依存関係を追加できます。 はScalaNative用に構築された依存関係のみを使用できることに注意することが重要です。

サポートされているライブラリを追加しましょう:

libraryDependencies ++= Seq(
  "com.lihaoyi" %%% "fansi" % "0.3.0"
)

groupIdの後の%%% シンボルは、これらのライブラリのScalaネイティブバージョンをダウンロードするようにSBTに通知します。

5.3. ネイティブアプリケーションとしてのパッケージ化

それでは、プロジェクトに簡単なScalaファイルを追加しましょう。

object Main {
  def main(args: Array[String]): Unit =
    println(fansi.Color.Green("Hello, world from Scala-Native!"))
}

SBTコマンドcompileを使用してコードをコンパイルできます。 次に、 nativeLink コマンドを使用して、この単純なプロジェクトのネイティブ実行可能ファイルを作成できます。

sbt nativeLink

これにより、 target /scala-2.13ディレクトリの下に実行可能ファイルが作成されます。 次に、このファイルを実行可能ファイルとして実行し、テキストをコンソールに出力します。

6. ネイティブの相互運用性

Scala Nativeは、多くの標準Cライブラリとの相互運用性を提供し、ネイティブCファイルとの直接統合をサポートします。

6.1. C標準ライブラリの相互運用性

Scala Nativeは、すでに多くの標準Cライブラリのバインディングを提供しています。 つまり、Cメソッドを直接呼び出すScalaコードを記述できるということです。 たとえば、Scalaコードでstring.hライブラリバインディングを使用してみましょう。

val s1 = "Scala"
val s2 = "Native"
val scalaNative: String = Zone { implicit z =>
  val scalaNativeC: CString = string.strcat(toCString(s1), toCString(s2))
  println(fromCString(scalaNativeC)) // Prints ScalaNative
  fromCString(scalaNativeC)
}

メソッドstring.strcat()は、基礎となるCメソッドを呼び出して2つの文字列を連結します。 ゾーンは、割り当てに必要なメモリ管理を行います。 さらに、ブロックが完了すると、Zoneは割り当てられたメモリも解放します

Scala Nativeは、c補間器を使用したCリテラル文字列もサポートしています。

val strLength: CSize = string.strlen(c"Hello ScalaNative")
println(s"Length of string is: "+strLength)

6.2. カスタムCコードの相互運用性

カスタムCコードを記述し、Scalaネイティブコードから簡単に呼び出すことができます。 まず、Cファイルをパス src / main / resources /scala-nativeの下に配置する必要があります。

#include <string.h>
#include <stdio.h>
int check_length(char arr[]) {
  int length = strlen(arr);
  printf("Length of String `%s` : %d ", arr, length);
  printf("\n");
  return length;
}

次に、オブジェクトを使用してScalaコードにバインディングを作成する必要があります。

@extern
object sample {
  def check_length(str: CString): Int = extern
}

@externアノテーションは、外部で定義されたコードを使用するコードをマークするために使用されます。次に、CとScalaの比較可能な型を使用して、対応するCコードのScalaメソッドを定義します。 メソッド本体のキーワードexternは、リンクライブラリで利用可能なネイティブコードで実装が提供されていることを示します。

これで、Scalaコードで次のようにメソッドに直接アクセスできます。

sample.check_length(c"Hello ScalaNative")

6.3. サードパーティのCライブラリの相互運用性

サードパーティのライブラリを使用する複雑な低レベルのコードをCで記述できます。 このコードは、通常のメソッドを呼び出すのと同じ方法でScalaNativeから呼び出すことができます。 ただし、必要なライブラリを事前にターゲットマシンにインストールする必要があります

libcurl ライブラリを使用して、CコードからURLを呼び出しましょう。

#include <stdlib.h>
#include <curl/curl.h>
void function_pt(void *ptr, size_t size, size_t nmemb, void *stream) {
    printf("%d", atoi(ptr));
}

int make_curl_call(char arr[]) {
  CURL *curl;
  CURLcode res;
  curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, arr);
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
    res = curl_easy_perform(curl);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, function_pt);
    if(res != CURLE_OK)
      fprintf(stderr, "curl_easy_perform() failed: %s\n",
              curl_easy_strerror(res));
    curl_easy_cleanup(curl);
  }
  return 0;
}

これで、Cメソッドシグネチャに対応するexternメソッドを定義できます。

@extern
@link("curl")
object testcurl {
  def make_curl_call(str: CString): Int = extern
}

@link アノテーションは、提供された外部ライブラリを使用しており、nativeLinkコマンド中にリンクする必要があることをコンパイラに通知します。 Cと同様に、ライブラリ名からlibプレフィックスを省略します。

これで、関連するURLを渡すことでメソッドを呼び出すことができます。

testcurl.make_curl_call(c"http://httpbin.org/uuid")

libcurlライブラリがビルドマシンとターゲットマシンの両方にインストールされていることを前提としています。

7. 結論

この記事では、Scala Nativeとは何か、そしてそれを使用してネイティブアプリケーションを構築する方法について説明しました。

いつものように、このチュートリアルで使用されるサンプルコードは、GitHubから入手できます。