はじめに

Adelie とは

Adelie(アデリー)は.Net互換の静的型付け言語です。RustとC#のメリットを活かした言語を目指しています。 Rustは速度や安全性において非常に優れた言語ですが、GUIアプリケーションの開発は難易度が高いです。一方のC#は.NET Frameworkなどの優れたライブラリが充実していますが、実行時エラーなどのバグに悩まされがちです。Adelieではそれらを組み合わせ、コンパイル時のチェックを強化した上で既存のC#ライブラリを使用することができます。

基本的な文法はRustに似ているため、Rustacean1であれば容易にコーディングすることが可能です。

特徴

  • 強力なデータ型
  • 既定ではimmutableな変数
  • 型推論
  • トレイト
  • ライフタイム
  • 所有権
  • パターンマッチング

影響を受けた言語との比較

※開発前の機能を含む

AdelieRustC#
アーキテクチャ.NETネイティブ.NET
パラダイムマルチパラダイムマルチパラダイムマルチパラダイム
型付け強い静的型付け強い静的型付け強い静的型付け
型推論✅️
ガベージコレクション✅️
シャドウイング✅️✅️
関数オーバーロード✅️
プロパティ✅️
クラス継承✅️
可変長引数✅️
メモリ安全✅️️✅️
所有権✅️✅️
ライフタイム✅️✅️
パターンマッチング✅️✅️
1

Rustacean: Rustを書く人のこと

事始め

  • Adelieのインストール

インストール

Windows10

必要なもの

ビルド

$ git clone https://github.com/kumavale/Adelie
$ make build

Linux

必要なもの

ビルド

$ git clone https://github.com/kumavale/Adelie
$ make build

近いうちにこうしたい

現状だとMakefileを使用しないと標準ライブラリ(組み込み関数)がビルド使えない。

$ cargo install --git https://github.com/kumavale/Adelie
$ adelie [file]...

構文

基本的な構文を示す。

データ型

Adelieでは全てのプリミティブ型を.Net型に置き換えることができます。

値型

整数型

Adelie範囲サイズ.NET型
i32-2,147,483,648 ~ 2,147,483,647符号付き32ビット整数System.Int32
i64-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807符号付き64ビット整数System.Int64

整数型は10進数での指定ができます。
接尾辞を付けない場合の規定はi32になります。

let a: i32 = 42;

浮動小数点数型

Adelieおおよその範囲有効桁数サイズ.NET型
f32±1.5 x 10−45 から ±3.4 x 1038~6-9 桁4 バイトSystem.Single

接尾辞を付けない場合の規定はf64になります。

let a: f32 = 3.14f32;
let b = 1.0;

論理値型

Adelie.NET型
boolSystem.Boolean

trueまたはfalseを指定することができます。

let a: bool = true;
let a: bool = false;

文字型

Adelie範囲サイズ.NET型
charU+0000 ~ U+FFFF16ビットSystem.Char

char型は内部でUnicode UTF-16文字を表します。

'(シングルクォート)」で囲った一文字を指定できます。

let a: char = 'a';
let a: char = '☺';

構造体型

Adelie.NET型
structSystem.ValueType
// 構造体定義
struct Rectangle {
    width: i32,
    height: i32,
}
// メソッド定義
impl Rectangle {
    fn new(w: i32, h: i32) -> Rectangle {
        Rectangle {
            w,
            h,
        }
    }
    fn area(&self) -> i32 {
        self.width * self.height
    }
}
fn main() {
    // インスタンス化
    let rect1: Rectangle = Rectangle::new(30, 50);
    assert_eq!(rect1.area(), 150);
}

列挙型

Adelie.NET型
enumSystem.Enum

内部では「0」からの連番を振られた名前付き定数になります。

enum Color {
    Red,
    Green,
    Blue,
}
fn main() {
    let blue: Color = Color::Blue;
    assert_eq!(blue as i32, 2);
}

参照型

文字列型

Adelie.NET型
stringSystem.String

Adelieではstring型はUTF-16のシーケンスを表します。

let a: string = "foo";

Box型

Adelie.NET型
BoxSystem.Object
let a: Box<i32> = Box::new(42);
assert_eq!(*a, 42);

Vec型

Adelie.NET型
VecSystem.Collections.Generic.List
let mut a: Vec<i32> = Vec::new();
a.push(42);
assert_eq!(a[0], 42);

クラス型

Adelie.NET型
classSystem.Object

後述するexternブロック内でのみ宣言が可能です。

extern {
    class Foo {
        bar: i32
    }
}

変数

宣言と代入

変数はletを使用して宣言します。
多くの場合、型名は省略可能です。

fn main() {
    let a: i32 = 42;
    let b = 42;
}

初期化は必須ではありませんが、未初期化の状態で変数を参照することはできません。

fn main() {
    let a: i32;
    let b: i32 = a;  // compile error
}

可変性

変数は標準で 不変 になります。

fn main() {
    let a: i32 = 42;
    a = 57;  // compile error
}

値を変更したい場合は、mutキーワードを使用して可変にすることができます。

fn main() {
    let mut a: i32 = 42;
    a = 57;  // OK!!
}

シャドーイング

スコープ毎に現れた同名の変数は前の変数を覆い隠します。
また、変数の型は同一である必要はありません。

fn main() {
    let a: i32 = 1;
    let a: i32 = a + 1;

    {
        let a: i32 = 5;
        assert_eq!(a, 5);
    }

    assert_eq!(a, 2);
}

制御フロー

if式

Adelieのif であるため、値を返します。
ifelseの型が一致している場合のみコンパイルが通り、変数に束縛することができます。

fn main() {
    let num: i32 = 42;

    if num % 15 == 0 {
        println!("FizzBuzz");
    } else if num % 3 == 0 {
        println!("Fizz");
    } else if num % 5 == 0 {
        println!("Buzz");
    } else {
        println!(num);
    }
}
fn main() {
    let winner: bool = true;
    let score: i32 = if winner { 33 } else { 4 };

    assert_eq!(score, 33);
}

ループ

ループにはloopまたはwhileを使用できます。

for文の対応はしばしお待ちを!

fn main() {
    loop {
        break;
    }

    let mut i: i32 = 3;
    while i > 0 {
        i -= 1;
    }
}

関数

fnから始まる文は関数宣言になります。
Adelieではmain()から始まる関数がエントリーポイントになります。

fn main() {
    foo();
}
fn foo() {
    println!("foo!");
}

引数

仮引数名及び型名を指定することで、引数を受け取ることができます。

fn main() {
    area(30, 50);
}
fn area(width: i32, height: i32) {
    println!(width * height);
}

戻り値

戻り値を指定しない場合は、voidが返されます。
しかし、「->」の後に型名を指定することで、値を返すことができます。

fn main() {
    assert_eq!(area(30, 50), 150);
}
fn area(width: i32, height: i32) -> i32 {
    return width * height;
}

最後の式はreturn;を省略することができます。

fn main() {
    assert_eq!(area(30, 50), 150);
}
fn area(width: i32, height: i32) -> i32 {
    width * height
}

コメント

行コメント

//から行末までの文字は行コメントになります。

// this is a comment
println!(1 + 1);  // comment too

ブロックコメント

/*から*/で囲まれた範囲の文字はブロックコメントになります。
ネスト、複数行も可能です。

/* comment */
/*
    /* comment
       // comment
    */
*/
println!(/* 1 + */ 1);  // 1

外部dll

externを使用することで、外部のマネージドdllに定義されている、class,struct,enum,functionを使用することができます。

#[link(name="System.Console.dll", publickeytoken="B0 3F 5F 7F 11 D5 0A 3A")]
extern {
    mod System {
        struct Console {}
        impl Console {
            fn WriteLine(_: string) {}
        }
    }
}

fn main() {
    System::Console::WriteLine("hello");
}

link属性にてnameにdll名、publickeytokenにパブリックキートークンを指定します。
.Netでいうところの「名前空間」はmodキーワードで再現します。
dll内で定義されている全てを宣言する必要はありません。Adelie側で使用する構造体名やフィールド名のみの宣言で問題ありません。

標準ライブラリ

Adelieの標準ライブラリについて記載する。

組み込み関数

組み込み関数は、識別名の後に「!」を付けることで呼び出すことができる。

print!

内部でSystem.Console.Write()を呼び出し、コンソールに標準出力を行う。
第一引数にstring型を指定した場合、コンパイル時にフォーマットを検査する。

引数説明
print!(i32)Write(Int32)と同等の処理
print!(f32)Write(Single)と同等の処理
print!(bool)Write(Boolean)と同等の処理
print!(char)Write(Char)と同等の処理
print!(string)Write(String)と同等の処理
print!(string, ...)Write(String, Object[])と同等の処理
print!("hello");
print!(42);
print!("{}", 42);

println!

内部でSystem.Console.WriteLine()を呼び出し、コンソールに標準出力を行う。
第一引数にstring型を指定した場合、コンパイル時にフォーマットを検査する。

引数説明
println!()WriteLine()と同等の処理
println!(i32)WriteLine(Int32)と同等の処理
println!(f32)WriteLine(Single)と同等の処理
println!(bool)WriteLine(Boolean)と同等の処理
println!(char)WriteLine(Char)と同等の処理
println!(string)WriteLine(String)と同等の処理
println!(string, ...)WriteLine(String, Object[])と同等の処理
println!();
println!("hello");
println!(42);
println!("{}", 42);

read_line!

内部でSystem.Console.ReadLine()を呼び出し、コンソールからの入力を待つ。

引数説明
read_line!()ReadLine()を呼び、stringを返す。
let input: string = read_line!();

assert!

実行時に、与えられた真偽値がtrueであることをアサートする。
アサートに失敗した場合はpanic!し、プログラムは終了する。

assert!(true);
assert!(1 == 1);
assert!(1 != 2);

assert_eq!

実行時に、与えられた両辺が同値であることをアサートする。
アサートに失敗した場合はpanic!し、プログラムは終了する。

assert_eq!(true, true);
assert_eq!(1, 1);
assert_eq!("foo", "foo");

panic!

内部でSystem.Environment.Exit(Int32)を呼び出し、現在のプログラムを終了する。

panic!();
panic!("panic with a message: {}", 42);

std

unimplemented!

サンプルプログラム

  • 「Hello, world!」を表示する最もシンプルなプログラム
  • WindowsFormsを使用したカウンターアプリ

Hello, world!

プログラムはfn main()から始まります。
プログラムの拡張子は「.ad」を推奨します。

// main.ad
fn main() {
    println!("Hello, world!");
}
$ make main.ad
Hello, world!

Windows Forms Application

counter gif

Adelieでは「プロパティ」をサポートしていないため、dllからプロパティを呼び出す際には「set_」または「get_」を接頭辞にする必要があります。また、イベントハンドラの追加には「add_」を接頭辞にします。

.ctor()は特別な名前であり、externブロック内でのみ宣言できます。これはdllからコンストラクタを呼び出すことができます。その他、classnested class、継承もexternブロック内でのみ使用可能です。

externが存在感を出していますが、将来的にこの部分を外部ライブラリにすることで、使用者はuse宣言のみで使用できるようにしたい狙いがあります。

#[link(name="System.Windows.Forms.dll", publickeytoken="B7 7A 5C 56 19 34 E0 89")]
extern {
    mod System {
        mod Windows {
            mod Forms {
                class Control {
                    class ControlCollection {}
                    impl ControlCollection {
                        fn Add(&self, value: Control) {}
                    }
                }
                impl Control {
                    fn set_Text(&self, _: string) {}
                    fn set_Location(&self, _: System::Drawing::Point) {}
                    fn set_Size(&self, _: System::Drawing::Size) {}
                    fn set_ClientSize(&self, _: System::Drawing::Size) {}
                    fn set_Font(&self, _: System::Drawing::Font) {}
                    fn get_Controls(&self) -> ControlCollection {}
                    fn add_Click(&self, event: System::EventHandler) {}
                }
                class ScrollableControl : Control {}
                class ContainerControl : ScrollableControl {}
                class Form : ContainerControl {}
                impl Form {
                    fn .ctor() {}
                }
                class Label : Control {}
                impl Label {
                    fn .ctor() {}
                    fn set_TextAlign(&self, _: System::Drawing::ContentAlignment) {}
                }
                class ButtonBase : Control {}
                class Button : ButtonBase {}
                impl Button {
                    fn .ctor() {}
                }
                class Application {}
                impl Application {
                    fn Run(_: Form) {}
                }
            }
        }
    }
}
#[link(name="mscorlib.dll")]
extern {
    mod System {
        class EventHandler {}
        class EventArgs {}
    }
}
#[link(name="System.Drawing.dll", publickeytoken="B0 3F 5F 7F 11 D5 0A 3A")]
extern {
    mod System {
        mod Drawing {
            class Font {}
            impl Font {
                fn .ctor(familyName: string, emSize: f32) {}
            }
            enum ContentAlignment {
                MiddleCenter = 32,
            }
        }
    }
}
#[link(name="System.Drawing.Primitives.dll", publickeytoken="B0 3F 5F 7F 11 D5 0A 3A")]
extern {
    mod System {
        mod Drawing {
            struct Point {}
            impl Point {
                fn .ctor(x: i32, y: i32) {}
            }
            struct Size {}
            impl Size {
                fn .ctor(w: i32, h: i32) {}
            }
        }
    }
}
fn main() {
    let mut form: System::Windows::Forms::Form = System::Windows::Forms::Form::.ctor();
    form.set_Text("Counter");
    form.set_ClientSize(System::Drawing::Size::.ctor(300, 200));

    let mut label: System::Windows::Forms::Label = System::Windows::Forms::Label::.ctor();
    label.set_Text("1");
    label.set_TextAlign(System::Drawing::ContentAlignment::MiddleCenter);
    label.set_Size(System::Drawing::Size::.ctor(300, 64));
    label.set_Location(System::Drawing::Point::.ctor(0, 50));
    label.set_Font(System::Drawing::Font::.ctor("Arial", 32.0f32));

    let mut button: System::Windows::Forms::Button = System::Windows::Forms::Button::.ctor();
    button.set_Text("Click Me!");
    button.set_Location(System::Drawing::Point::.ctor(115, 140));

    form.get_Controls().Add(label);
    form.get_Controls().Add(button);

    let mut count: i32 = 1;
    button.add_Click(|sender: Box<System::Windows::Forms::Button>, e: System::EventArgs| {
        count += 1;
        label.set_Text(count.to_string());
    });

    System::Windows::Forms::Application::Run(form);
}