課題18: unsafe Rust
マンダトリー要件
問題1: rawポインタの操作(30点)
rawポインタを使って安全な抽象化を作成しなさい。
// カスタムVecの簡易版を実装
struct SimpleVec<T> {
ptr: *mut T,
len: usize,
capacity: usize,
}
impl<T> SimpleVec<T> {
fn new() -> Self {
// TODO: 実装 (5点)
}
fn push(&mut self, value: T) {
// TODO: 実装 (10点)
// キャパシティが不足したら再割り当て
}
fn get(&self, index: usize) -> Option<&T> {
// TODO: 実装 (5点)
}
fn len(&self) -> usize {
// TODO: 実装 (2点)
}
fn capacity(&self) -> usize {
// TODO: 実装 (2点)
}
}
impl<T> Drop for SimpleVec<T> {
fn drop(&mut self) {
// TODO: 実装 (6点)
// メモリリークを防ぐ
}
}
fn main() {
let mut vec = SimpleVec::new();
vec.push(1);
vec.push(2);
vec.push(3);
assert_eq!(vec.len(), 3);
assert_eq!(vec.get(0), Some(&1));
assert_eq!(vec.get(1), Some(&2));
assert_eq!(vec.get(2), Some(&3));
assert_eq!(vec.get(3), None);
println!("Problem 1 passed!");
}
評価基準:
newの実装: 5点pushの実装: 10点getの実装: 5点len/capacityの実装: 4点Dropの実装: 6点
---
問題2: FFI - Cライブラリの呼び出し(30点)
標準Cライブラリの関数を呼び出すバインディングを作成しなさい。
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
// libc関数の宣言
extern "C" {
// TODO: 以下のC関数を宣言 (10点)
// - strlen: 文字列の長さを返す
// - strcmp: 2つの文字列を比較
// - getpid: プロセスIDを取得
}
// safeなラッパー関数を実装
fn safe_strlen(s: &str) -> usize {
// TODO: 実装 (10点)
}
fn safe_strcmp(s1: &str, s2: &str) -> i32 {
// TODO: 実装 (10点)
}
fn main() {
// テスト
assert_eq!(safe_strlen("Hello, World!"), 13);
assert_eq!(safe_strcmp("abc", "abc"), 0);
assert!(safe_strcmp("abc", "def") < 0);
assert!(safe_strcmp("def", "abc") > 0);
println!("Problem 2 passed!");
}
評価基準:
- extern宣言: 10点
safe_strlenの実装: 10点safe_strcmpの実装: 10点
---
問題3: static変数と同期化(20点)
グローバルカウンターをスレッドセーフに実装しなさい。
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
// TODO: グローバルカウンターを実装 (10点)
static COUNTER: AtomicUsize = AtomicUsize::new(0);
fn increment() {
// TODO: 実装 (5点)
// カウンターをアトミックにインクリメント
}
fn get_count() -> usize {
// TODO: 実装 (5点)
}
fn main() {
let mut handles = vec![];
// 10個のスレッドで各1000回インクリメント
for _ in 0..10 {
let handle = thread::spawn(|| {
for _ in 0..1000 {
increment();
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(get_count(), 10000);
println!("Problem 3 passed!");
}
評価基準:
- グローバル変数の宣言: 10点
incrementの実装: 5点get_countの実装: 5点
---
ボーナス課題
ボーナス1: カスタムLinkedList(10点)
unsafeを使って単方向連結リストを実装しなさい。
use std::ptr;
struct Node<T> {
data: T,
next: *mut Node<T>,
}
struct LinkedList<T> {
head: *mut Node<T>,
len: usize,
}
impl<T> LinkedList<T> {
fn new() -> Self {
// TODO: 実装
}
fn push_front(&mut self, data: T) {
// TODO: 実装
}
fn pop_front(&mut self) -> Option<T> {
// TODO: 実装
}
fn len(&self) -> usize {
self.len
}
}
impl<T> Drop for LinkedList<T> {
fn drop(&mut self) {
// TODO: 実装
// すべてのノードを解放
}
}
fn main() {
let mut list = LinkedList::new();
list.push_front(1);
list.push_front(2);
list.push_front(3);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(3));
assert_eq!(list.pop_front(), Some(2));
assert_eq!(list.pop_front(), Some(1));
assert_eq!(list.pop_front(), None);
println!("Bonus 1 passed!");
}
評価基準:
- 基本的な実装: 5点
- メモリ管理の正しさ: 5点
---
ボーナス2: Cの構造体との相互運用(10点)
Cと互換性のある構造体を定義し、操作しなさい。
use std::ffi::CString;
use std::os::raw::c_char;
// C互換の構造体
#[repr(C)]
struct Person {
name: *const c_char,
age: u32,
}
impl Person {
fn new(name: &str, age: u32) -> Self {
// TODO: 実装
// CStringを使って安全に変換
}
fn get_name(&self) -> String {
// TODO: 実装
// CStrから安全にStringに変換
}
fn get_age(&self) -> u32 {
self.age
}
}
impl Drop for Person {
fn drop(&mut self) {
// TODO: 実装
// nameのメモリを解放
}
}
fn main() {
let person = Person::new("Alice", 25);
assert_eq!(person.get_name(), "Alice");
assert_eq!(person.get_age(), 25);
println!("Bonus 2 passed!");
}
評価基準:
- C互換構造体の定義: 3点
- 文字列の変換: 4点
- メモリ管理: 3点
---
ボーナス3: インラインアセンブリ(10点)
インラインアセンブリを使って基本的な操作を実装しなさい。
#![feature(asm)]
use std::arch::asm;
fn add_asm(a: u64, b: u64) -> u64 {
// TODO: 実装
// インラインアセンブリでa + bを計算
}
fn multiply_asm(a: u64, b: u64) -> u64 {
// TODO: 実装
// インラインアセンブリでa * bを計算
}
fn main() {
assert_eq!(add_asm(5, 3), 8);
assert_eq!(multiply_asm(5, 3), 15);
println!("Bonus 3 passed!");
}
評価基準:
add_asmの実装: 5点multiply_asmの実装: 5点- 簡単なCライブラリを作成
- bindgenでバインディングを生成
- Rustから呼び出し
注: nightly Rustが必要です。
---
ボーナス4: bindgenを使ったバインディング生成(10点)
bindgenを使って実際のCライブラリのバインディングを生成しなさい。
タスク:
C側(mylib.h):
#ifndef MYLIB_H
#define MYLIB_H
typedef struct {
int x;
int y;
} Point;
Point point_new(int x, int y);
int point_distance_squared(Point* p1, Point* p2);
#endif
Rust側:
// build.rsでbindgenを設定
// 生成されたバインディングを使用
fn main() {
// TODO: 実装
// bindgenで生成されたコードを使用
}
評価基準:
- Cライブラリの作成: 3点
- bindgenの設定: 4点
- バインディングの使用: 3点
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:rawポインタ | 30点 | メモリ管理の正しさ |
| 問題2:FFI | 30点 | Cライブラリとの連携 |
| 問題3:static変数 | 20点 | スレッドセーフな実装 |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:LinkedList | 10点 | unsafeの正しい使用 |
| ボーナス2:C互換構造体 | 10点 | 相互運用性 |
| ボーナス3:アセンブリ | 10点 | 低レベル操作 |
| ボーナス4:bindgen | 10点 | 実用的なツール |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-foundations-18/
├── src/
│ ├── problem1.rs
│ ├── problem2.rs
│ ├── problem3.rs
│ ├── bonus1.rs # オプション
│ ├── bonus2.rs # オプション
│ ├── bonus3.rs # オプション
│ └── bonus4/ # オプション
│ ├── build.rs
│ ├── mylib.h
│ └── src/main.rs
├── Cargo.toml
└── README.md
テスト
cargo test --release
# ボーナス3(nightly必要)
cargo +nightly test --features asm
提出期限
---
ヒント
問題1のヒント
// メモリ割り当て
use std::alloc::{alloc, dealloc, Layout};
let layout = Layout::array::<T>(capacity).unwrap();
let ptr = alloc(layout) as *mut T;
// 要素の書き込み
ptr::write(self.ptr.add(self.len), value);
問題2のヒント
fn safe_strlen(s: &str) -> usize {
let c_str = CString::new(s).unwrap();
unsafe {
strlen(c_str.as_ptr())
}
}
問題3のヒント
COUNTER.fetch_add(1, Ordering::SeqCst);
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
---
コース完了おめでとうございます!
Rust Foundationsコースを完了しました。これで:
次のステップ:
Happy Rusting! 🦀