はじめに
Adelie とは
Adelie(アデリー)は.Net互換の静的型付け言語です。RustとC#のメリットを活かした言語を目指しています。
Rustは速度や安全性において非常に優れた言語ですが、GUIアプリケーションの開発は難易度が高いです。一方のC#は.NET Framework
などの優れたライブラリが充実していますが、実行時エラーなどのバグに悩まされがちです。Adelieではそれらを組み合わせ、コンパイル時のチェックを強化した上で既存のC#ライブラリを使用することができます。
基本的な文法はRustに似ているため、Rustacean1であれば容易にコーディングすることが可能です。
特徴
- 強力なデータ型
- 既定ではimmutableな変数
- 型推論
- トレイト
- ライフタイム
- 所有権
- パターンマッチング
影響を受けた言語との比較
※開発前の機能を含む
Adelie | Rust | C# | |
---|---|---|---|
アーキテクチャ | .NET | ネイティブ | .NET |
パラダイム | マルチパラダイム | マルチパラダイム | マルチパラダイム |
型付け | 強い静的型付け | 強い静的型付け | 強い静的型付け |
型推論 | ✅️ | ✅ | ✅ |
ガベージコレクション | ✅️ | ✅ | |
シャドウイング | ✅️ | ✅️ | |
関数オーバーロード | ✅️ | ️ | ✅ |
プロパティ | ✅️ | ||
クラス継承 | ✅️ | ||
可変長引数 | ✅️ | ||
メモリ安全 | ✅️️ | ✅️ | |
所有権 | ✅️ | ✅️ | |
ライフタイム | ✅️ | ✅️ | |
パターンマッチング | ✅️ | ✅️ | ✅ |
Rustacean: Rustを書く人のこと
事始め
- Adelieのインストール
インストール
Windows10
必要なもの
- Visual Studio (
ilasm.exe
を使用) - Cargo
ビルド
$ 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型 |
---|---|
bool | System.Boolean |
true
またはfalse
を指定することができます。
let a: bool = true;
let a: bool = false;
文字型
Adelie | 範囲 | サイズ | .NET型 |
---|---|---|---|
char | U+0000 ~ U+FFFF | 16ビット | System.Char |
char
型は内部でUnicode UTF-16文字を表します。
「'
(シングルクォート)」で囲った一文字を指定できます。
let a: char = 'a';
let a: char = '☺';
構造体型
Adelie | .NET型 |
---|---|
struct | System.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型 |
---|---|
enum | System.Enum |
内部では「0」からの連番を振られた名前付き定数になります。
enum Color {
Red,
Green,
Blue,
}
fn main() {
let blue: Color = Color::Blue;
assert_eq!(blue as i32, 2);
}
参照型
文字列型
Adelie | .NET型 |
---|---|
string | System.String |
Adelieではstring
型はUTF-16のシーケンスを表します。
let a: string = "foo";
Box型
Adelie | .NET型 |
---|---|
Box | System.Object |
let a: Box<i32> = Box::new(42);
assert_eq!(*a, 42);
Vec型
Adelie | .NET型 |
---|---|
Vec | System.Collections.Generic.List |
let mut a: Vec<i32> = Vec::new();
a.push(42);
assert_eq!(a[0], 42);
クラス型
Adelie | .NET型 |
---|---|
class | System.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
は 式 であるため、値を返します。
if
とelse
の型が一致している場合のみコンパイルが通り、変数に束縛することができます。
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
Adelieでは「プロパティ」をサポートしていないため、dllからプロパティを呼び出す際には「set_
」または「get_
」を接頭辞にする必要があります。また、イベントハンドラの追加には「add_
」を接頭辞にします。
.ctor()
は特別な名前であり、extern
ブロック内でのみ宣言できます。これはdllからコンストラクタを呼び出すことができます。その他、class
、nested 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);
}