課題4: 上級所有権テクニック
マンダトリー要件(80点)
問題1: Unsafe Rustの実装(25点)
1.1 生ポインタの操作(10点)
以下の関数を実装しなさい:
/// 配列の要素を安全に交換する
///
/// # Safety
/// - `arr`は有効なメモリを指していること
/// - `len`は配列の実際の長さであること
/// - `i`と`j`は`len`未満であること
unsafe fn swap_elements(arr: *mut i32, len: usize, i: usize, j: usize) {
// ここを実装(5点)
}
fn main() {
let mut data = vec![1, 2, 3, 4, 5];
let ptr = data.as_mut_ptr();
let len = data.len();
unsafe {
swap_elements(ptr, len, 0, 4);
}
assert_eq!(data, vec![5, 2, 3, 4, 1]);
}
評価ポイント:
- 境界チェックの実装(2点)
- 生ポインタの正しい操作(2点)
- Safety契約の順守(1点)
追加課題(5点):
- 安全なラッパー関数
safe_swapを実装しなさい safe_swapからunsafe関数を呼び出す- 境界チェックを行い、エラーを返す
pub fn safe_swap(arr: &mut [i32], i: usize, j: usize) -> Result<(), &'static str> {
// ここを実装
}
1.2 Vecの簡易実装(15点)
Vecの簡易版を実装しなさい:
use std::alloc::{alloc, dealloc, realloc, Layout};
use std::ptr;
pub struct SimpleVec<T> {
ptr: *mut T,
len: usize,
capacity: usize,
}
impl<T> SimpleVec<T> {
pub fn new() -> Self {
// ここを実装(2点)
}
pub fn push(&mut self, value: T) {
// ここを実装(5点)
// キャパシティが足りなければ再アロケーション
}
pub fn pop(&mut self) -> Option<T> {
// ここを実装(3点)
}
pub fn len(&self) -> usize {
self.len
}
pub fn capacity(&self) -> usize {
self.capacity
}
}
impl<T> Drop for SimpleVec<T> {
fn drop(&mut self) {
// ここを実装(3点)
// メモリリークを防ぐ
// 各要素を適切にドロップ
}
}
// 安全性のテスト(2点)
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_vec() {
let mut v = SimpleVec::new();
v.push(1);
v.push(2);
v.push(3);
assert_eq!(v.len(), 3);
assert_eq!(v.pop(), Some(3));
assert_eq!(v.len(), 2);
}
}
---
問題2: FFI(Foreign Function Interface)(25点)
2.1 C関数のラッパー(15点)
以下のC関数のRustラッパーを実装しなさい:
Cコード:
// mylib.h
typedef struct {
int x;
int y;
} Point;
Point* point_new(int x, int y);
void point_free(Point* p);
void point_translate(Point* p, int dx, int dy);
int point_distance_squared(const Point* p);
Rustコード:
use std::os::raw::c_int;
#[repr(C)]
struct Point {
x: c_int,
y: c_int,
}
extern "C" {
fn point_new(x: c_int, y: c_int) -> *mut Point;
fn point_free(p: *mut Point);
fn point_translate(p: *mut Point, dx: c_int, dy: c_int);
fn point_distance_squared(p: *const Point) -> c_int;
}
/// Rustの安全なラッパー
pub struct RustPoint {
ptr: *mut Point,
}
impl RustPoint {
pub fn new(x: i32, y: i32) -> Self {
// ここを実装(3点)
// C関数を呼び出す
}
pub fn translate(&mut self, dx: i32, dy: i32) {
// ここを実装(3点)
}
pub fn distance_squared(&self) -> i32 {
// ここを実装(3点)
}
pub fn coords(&self) -> (i32, i32) {
// ここを実装(2点)
// ポインタから値を読み取る
}
}
impl Drop for RustPoint {
fn drop(&mut self) {
// ここを実装(2点)
// C側のメモリを解放
}
}
// Send/Syncの実装判断(2点)
// 実装すべきか?その理由は?
2.2 文字列のFFI(10点)
以下のC関数とやり取りする関数を実装しなさい:
// C側
char* process_string(const char* input);
void free_c_string(char* s);
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
extern "C" {
fn process_string(input: *const c_char) -> *mut c_char;
fn free_c_string(s: *mut c_char);
}
/// RustのStringをC関数で処理して返す
pub fn rust_process_string(input: &str) -> Result<String, Box<dyn std::error::Error>> {
// ここを実装(10点)
// 1. Rust &str → CString(2点)
// 2. C関数の呼び出し(2点)
// 3. C char* → Rust String(3点)
// 4. C側メモリの解放(2点)
// 5. エラーハンドリング(1点)
}
---
問題3: Pinと自己参照構造体(20点)
3.1 自己参照構造体の実装(15点)
以下の仕様を満たす自己参照構造体を実装しなさい:
use std::pin::Pin;
use std::marker::PhantomPinned;
/// データとそのスライスへの参照を持つ構造体
struct SelfRefSlice {
data: Vec<u8>,
slice_ptr: *const [u8], // dataの一部を指す
_pin: PhantomPinned,
}
impl SelfRefSlice {
/// 新しいインスタンスを作成
/// start..endの範囲をslice_ptrで指す
pub fn new(data: Vec<u8>, start: usize, end: usize) -> Pin<Box<Self>> {
// ここを実装(7点)
// 1. 構造体を作成(2点)
// 2. ヒープに確保(2点)
// 3. slice_ptrを正しく設定(2点)
// 4. Pinで固定(1点)
}
/// dataの内容を取得
pub fn get_data(self: Pin<&Self>) -> &[u8] {
// ここを実装(2点)
}
/// slice_ptrが指す内容を取得
pub fn get_slice(self: Pin<&Self>) -> &[u8] {
// ここを実装(3点)
// unsafeブロックが必要
}
/// slice_ptrの範囲を変更
pub fn update_slice(self: Pin<&mut Self>, start: usize, end: usize) {
// ここを実装(3点)
// unsafeブロックが必要
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_self_ref_slice() {
let data = vec![1, 2, 3, 4, 5];
let s = SelfRefSlice::new(data, 1, 4);
assert_eq!(s.as_ref().get_data(), &[1, 2, 3, 4, 5]);
assert_eq!(s.as_ref().get_slice(), &[2, 3, 4]);
}
}
3.2 Pin APIの理解(5点)
以下の質問に答えなさい:
- Pin::new vs Pin::new_unchecked(2点)
- Unpinトレイト(2点)
- Pin<&mut T> vs Pin
> (1点)
---
問題4: メモリレイアウト(10点)
4.1 repr属性の実装(10点)
以下の構造体について、適切なrepr属性を選択し、理由を説明しなさい:
// ケース1: C言語のネットワークパケット構造体
struct PacketHeader {
version: u8,
flags: u8,
length: u16,
checksum: u32,
}
// ケース2: ファイルディスクリプタのラッパー
struct FileDescriptor(i32);
// ケース3: メモリ効率を最大化したい構造体
struct CompactData {
a: u8,
b: u32,
c: u8,
}
// ケース4: 共用体
union FloatOrInt {
f: f32,
i: u32,
}
各ケースについて:
- 適切な
repr属性を選択(各1点、計4点) - 理由を説明(各1.5点、計6点)
---
ボーナス課題(20点)
ボーナス1: カスタムアロケータの実装(10点)
グローバルアロケータを実装しなさい:
use std::alloc::{GlobalAlloc, Layout};
use std::sync::atomic::{AtomicUsize, Ordering};
/// アロケーション統計を記録するアロケータ
struct CountingAllocator;
static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
static DEALLOCATED: AtomicUsize = AtomicUsize::new(0);
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// ここを実装(3点)
// System allocatorを使用
// 統計を更新
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
// ここを実装(3点)
// System allocatorを使用
// 統計を更新
}
}
#[global_allocator]
static GLOBAL: CountingAllocator = CountingAllocator;
pub fn get_allocated() -> usize {
ALLOCATED.load(Ordering::SeqCst)
}
pub fn get_deallocated() -> usize {
DEALLOCATED.load(Ordering::SeqCst)
}
pub fn get_current_usage() -> usize {
get_allocated() - get_deallocated()
}
追加要件(4点):
- アロケーション失敗時の処理
- スレッドセーフの保証
- テストコード
---
ボーナス2: FFIでコールバック関数(10点)
C言語のコールバック関数をRustで実装しなさい:
Cコード:
// C側
typedef int (*callback_t)(int);
void process_array(int* arr, size_t len, callback_t cb);
Rustコード:
use std::os::raw::c_int;
type Callback = extern "C" fn(c_int) -> c_int;
extern "C" {
fn process_array(arr: *mut c_int, len: usize, cb: Callback);
}
// Rustのクロージャをコールバックに変換
pub fn rust_process_array<F>(arr: &mut [i32], f: F)
where
F: Fn(i32) -> i32,
{
// ここを実装(10点)
// 課題:
// 1. クロージャをextern "C"関数に変換
// 2. クロージャのキャプチャを扱う
// 3. トランポリン関数パターンを使用
}
// 使用例
fn main() {
let mut data = vec![1, 2, 3, 4, 5];
let multiplier = 2;
rust_process_array(&mut data, |x| x * multiplier);
assert_eq!(data, vec![2, 4, 6, 8, 10]);
}
ヒント: Box::into_rawとBox::from_rawを使用
---
ボーナス3: Async Executorの基礎実装(10点)
簡易的な非同期ランタイムを実装しなさい:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::sync::{Arc, Mutex};
use std::collections::VecDeque;
type Task = Pin<Box<dyn Future<Output = ()>>>;
struct SimpleExecutor {
tasks: Arc<Mutex<VecDeque<Task>>>,
}
impl SimpleExecutor {
fn new() -> Self {
// ここを実装
}
fn spawn(&self, future: impl Future<Output = ()> + 'static) {
// ここを実装(3点)
}
fn run(&self) {
// ここを実装(7点)
// 1. タスクキューからタスクを取得
// 2. Wakerを作成
// 3. Futureをpoll
// 4. Pendingなら再度キューに追加
// 5. Readyなら完了
}
}
// Wakerの実装
fn create_waker(tasks: Arc<Mutex<VecDeque<Task>>>) -> Waker {
// ここを実装
}
// テスト用の非同期関数
async fn example_task() {
println!("Task running");
}
fn main() {
let executor = SimpleExecutor::new();
executor.spawn(example_task());
executor.run();
}
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:Unsafe実装 | 25点 | 生ポインタの正確な操作、メモリ管理 |
| 問題2:FFI | 25点 | C/Rustの境界処理、安全性の保証 |
| 問題3:Pin | 20点 | 自己参照構造体の実装、Pinの理解 |
| 問題4:メモリレイアウト | 10点 | repr属性の適切な選択 |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:カスタムアロケータ | 10点 | GlobalAllocの正確な実装 |
| ボーナス2:FFIコールバック | 10点 | クロージャのFFI変換 |
| ボーナス3:Async Executor | 10点 | Future、Waker、Pinの統合 |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-ownership-deep-dive-04/
├── mandatory/
│ ├── src/
│ │ ├── unsafe_ops.rs
│ │ ├── ffi_wrapper.rs
│ │ ├── self_ref.rs
│ │ └── memory_layout.rs
│ ├── Cargo.toml
│ ├── build.rs # Cライブラリのビルド
│ └── tests/
│ └── integration.rs
├── bonus1-allocator/
│ └── src/lib.rs
├── bonus2-callback/
│ ├── src/
│ ├── c_lib/
│ │ ├── callback.c
│ │ └── callback.h
│ └── build.rs
└── bonus3-executor/
└── src/lib.rs
提出期限
- マンダトリー:第4章学習後、2週間以内
- ボーナス:コース修了時まで
---
ヒント
問題1のヒント(Unsafe)
生ポインタの操作:
unsafe {
// ポインタのオフセット
let elem = ptr.add(index);
// 読み取り
let value = elem.read();
// 書き込み
elem.write(new_value);
// スワップ
std::ptr::swap(ptr1, ptr2);
}
メモリアロケーション:
use std::alloc::{alloc, dealloc, Layout};
unsafe {
let layout = Layout::array::<i32>(10).unwrap();
let ptr = alloc(layout) as *mut i32;
// 使用...
dealloc(ptr as *mut u8, layout);
}
問題2のヒント(FFI)
C関数の呼び出しパターン:
// パターン1: シンプルな関数
extern "C" {
fn c_function(x: c_int) -> c_int;
}
unsafe {
let result = c_function(42);
}
// パターン2: ポインタを返す関数
extern "C" {
fn create_object() -> *mut Object;
fn destroy_object(obj: *mut Object);
}
pub struct SafeObject {
ptr: *mut Object,
}
impl Drop for SafeObject {
fn drop(&mut self) {
unsafe {
destroy_object(self.ptr);
}
}
}
問題3のヒント(Pin)
自己参照構造体のパターン:
use std::pin::Pin;
use std::marker::PhantomPinned;
struct SelfRef {
data: String,
ptr: *const String,
_pin: PhantomPinned,
}
impl SelfRef {
fn new(data: String) -> Pin<Box<Self>> {
let mut boxed = Box::new(SelfRef {
data,
ptr: std::ptr::null(),
_pin: PhantomPinned,
});
let data_ptr: *const String = &boxed.data;
boxed.ptr = data_ptr;
unsafe { Pin::new_unchecked(boxed) }
}
}
問題4のヒント(repr)
選択基準:
// C互換性が必要
#[repr(C)]
struct CCompat { }
// 透過的なラッパー
#[repr(transparent)]
struct Wrapper(Inner);
// パックされた構造体
#[repr(packed)]
struct Packed { }
// アライメント指定
#[repr(align(16))]
struct Aligned { }
ボーナス1のヒント(アロケータ)
GlobalAllocの実装:
use std::alloc::{GlobalAlloc, System, Layout};
struct MyAllocator;
unsafe impl GlobalAlloc for MyAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
// 実際のアロケーションはSystemに委譲
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout)
}
}
ボーナス2のヒント(コールバック)
トランポリンパターン:
// extern "C"関数(トランポリン)
extern "C" fn trampoline<F>(value: c_int, data: *mut c_void) -> c_int
where
F: Fn(i32) -> i32,
{
let closure = unsafe { &*(data as *const F) };
closure(value as i32) as c_int
}
// 使用例
let closure = |x: i32| x * 2;
let closure_ptr = &closure as *const _ as *mut c_void;
unsafe {
c_function_with_callback(trampoline::<_>, closure_ptr);
}
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
- [ ] Unsafeの5つの超能力
- [ ] 生ポインタの安全な操作
- [ ] メモリアロケーションとドロップ
- [ ] FFIでのC/Rust境界処理
- [ ] 文字列のFFI変換
- [ ] Pin
と自己参照構造体 - [ ] メモリレイアウトとrepr属性
- [ ] カスタムアロケータの実装
- [ ] 非同期プログラミングの基礎
コースを修了しました!次のステップ:
- 実際のプロジェクトでの応用
- async/awaitの深掘り
- コンパイラの内部実装の学習
- The Rustonomicon
---
参考資料
公式ドキュメント
- std::pin
- FFI Guide
追加リソース
- "Pin, Unpin, and why Rust needs them"
- "Learning Rust With Entirely Too Many Linked Lists"
- "The Rust Performance Book"
- Miri - Rust Interpreter
コミュニティリソース
- Rust Users Forum
- /r/rust
- Rust Async Book