課題5: 参照と借用の実践
マンダトリー要件
問題1:借用ルールの理解(25点)
以下のコードについて、コンパイルエラーが発生するか判定し、理由を説明しなさい。
// 1-1
fn main() {
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
}
// 1-2
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
// 1-3
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
// 1-4
fn main() {
let mut s = String::from("hello");
let r1 = &s;
println!("{}", r1);
let r2 = &mut s;
println!("{}", r2);
}
// 1-5
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
// 1-6
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
各コードについて:
- コンパイルできるか?
- エラーの場合、どのルールに違反しているか?
- どう修正するか?
提出物:problem1_analysis.md
問題2:参照を使った関数実装(25点)
以下の関数を参照と借用を使って実装しなさい。
/// 文字列の長さを返す(不変参照)
fn string_length(s: &String) -> usize {
// TODO: 実装
}
/// 文字列に接尾辞を追加(可変参照)
fn append_suffix(s: &mut String, suffix: &str) {
// TODO: 実装
}
/// 最初の単語を返す(スライスを返す)
fn first_word(s: &str) -> &str {
// TODO: 実装
// ヒント:スペースを見つけてその前までを返す
}
/// 配列の最大値を返す(参照を返す)
fn find_max<'a>(numbers: &'a [i32]) -> Option<&'a i32> {
// TODO: 実装
}
/// 2つの文字列のうち長い方を返す
fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {
// TODO: 実装
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_length() {
let s = String::from("hello");
assert_eq!(string_length(&s), 5);
assert_eq!(s, "hello"); // sはまだ有効
}
#[test]
fn test_append_suffix() {
let mut s = String::from("hello");
append_suffix(&mut s, " world");
assert_eq!(s, "hello world");
}
#[test]
fn test_first_word() {
assert_eq!(first_word("hello world"), "hello");
assert_eq!(first_word("hello"), "hello");
}
#[test]
fn test_find_max() {
let numbers = [1, 5, 3, 9, 2];
assert_eq!(find_max(&numbers), Some(&9));
let empty: [i32; 0] = [];
assert_eq!(find_max(&empty), None);
}
#[test]
fn test_longer() {
assert_eq!(longer("hello", "world!"), "world!");
assert_eq!(longer("rust", "go"), "rust");
}
}
提出物:problem2_references.rs
問題3:文字列処理(25点)
文字列処理ライブラリを実装しなさい。
/// 文字列を反転する(元の文字列を変更)
fn reverse_string(s: &mut String) {
// TODO: 実装
// ヒント:chars().rev().collect()
}
/// 文字列から空白を削除する
fn remove_whitespace(s: &mut String) {
// TODO: 実装
}
/// 文字列を単語に分割する
fn split_words(s: &str) -> Vec<&str> {
// TODO: 実装
// ヒント:split_whitespace()
}
/// パリンドローム(回文)かどうか判定
fn is_palindrome(s: &str) -> bool {
// TODO: 実装
// ヒント:大文字小文字を無視、空白を無視
}
/// 文字列内の単語数をカウント
fn count_words(s: &str) -> usize {
// TODO: 実装
}
/// 指定された文字列が含まれているか
fn contains_substring(haystack: &str, needle: &str) -> bool {
// TODO: 実装
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_reverse_string() {
let mut s = String::from("hello");
reverse_string(&mut s);
assert_eq!(s, "olleh");
}
#[test]
fn test_remove_whitespace() {
let mut s = String::from("hello world rust");
remove_whitespace(&mut s);
assert_eq!(s, "helloworldrust");
}
#[test]
fn test_split_words() {
let s = "hello world rust";
let words = split_words(s);
assert_eq!(words, vec!["hello", "world", "rust"]);
}
#[test]
fn test_is_palindrome() {
assert!(is_palindrome("racecar"));
assert!(is_palindrome("A man a plan a canal Panama"));
assert!(!is_palindrome("hello"));
}
#[test]
fn test_count_words() {
assert_eq!(count_words("hello world"), 2);
assert_eq!(count_words(" hello world "), 2);
}
}
提出物:problem3_string_utils.rs
問題4:配列処理(25点)
配列操作関数を実装しなさい。
/// 配列の合計を計算
fn sum(numbers: &[i32]) -> i32 {
// TODO: 実装
}
/// 配列の平均を計算
fn average(numbers: &[i32]) -> f64 {
// TODO: 実装
}
/// 配列の最小値と最大値を返す
fn min_max(numbers: &[i32]) -> Option<(i32, i32)> {
// TODO: 実装
// 空配列の場合はNone
}
/// 配列を逆順にする(元の配列を変更)
fn reverse_array(arr: &mut [i32]) {
// TODO: 実装
}
/// 配列から重複を削除(ソート済み配列を想定)
fn dedup(arr: &mut Vec<i32>) {
// TODO: 実装
// ヒント:Vec::dedup()
}
/// 配列の要素をすべて2倍にする
fn double_elements(arr: &mut [i32]) {
// TODO: 実装
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sum() {
assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
}
#[test]
fn test_average() {
assert_eq!(average(&[1, 2, 3, 4, 5]), 3.0);
}
#[test]
fn test_min_max() {
assert_eq!(min_max(&[3, 1, 4, 1, 5]), Some((1, 5)));
assert_eq!(min_max(&[]), None);
}
#[test]
fn test_reverse_array() {
let mut arr = [1, 2, 3, 4, 5];
reverse_array(&mut arr);
assert_eq!(arr, [5, 4, 3, 2, 1]);
}
#[test]
fn test_double_elements() {
let mut arr = [1, 2, 3, 4, 5];
double_elements(&mut arr);
assert_eq!(arr, [2, 4, 6, 8, 10]);
}
}
提出物:problem4_array_utils.rs
---
ボーナス課題
> ボーナス: 以下はオプションです。マンダトリー要件を完了してから挑戦してください。
ボーナス1:テキストエディタ(10点)
シンプルなテキストエディタのコア機能を実装しなさい。
struct TextBuffer {
content: String,
}
impl TextBuffer {
fn new() -> Self {
TextBuffer {
content: String::new(),
}
}
/// テキストを追加
fn append(&mut self, text: &str) {
// TODO: 実装
}
/// 指定位置にテキストを挿入
fn insert(&mut self, pos: usize, text: &str) {
// TODO: 実装
}
/// 指定範囲のテキストを削除
fn delete(&mut self, start: usize, end: usize) {
// TODO: 実装
}
/// テキスト全体を取得
fn content(&self) -> &str {
// TODO: 実装
}
/// 指定行を取得
fn get_line(&self, line_num: usize) -> Option<&str> {
// TODO: 実装
}
/// 行数を取得
fn line_count(&self) -> usize {
// TODO: 実装
}
/// 検索(マッチした行番号を返す)
fn search(&self, pattern: &str) -> Vec<usize> {
// TODO: 実装
}
/// 置換
fn replace_all(&mut self, from: &str, to: &str) {
// TODO: 実装
}
}
fn main() {
let mut buffer = TextBuffer::new();
buffer.append("Hello, World!\n");
buffer.append("This is Rust.\n");
buffer.insert(0, "First line\n");
println!("{}", buffer.content());
println!("行数: {}", buffer.line_count());
let results = buffer.search("Rust");
println!("検索結果: {:?}", results);
}
提出物:bonus1_text_editor/プロジェクト
ボーナス2:データ検証ライブラリ(10点)
参照を活用したバリデーションライブラリを作成しなさい。
struct Validator;
impl Validator {
/// 空でないことを検証
fn is_not_empty(s: &str) -> Result<(), &'static str> {
// TODO: 実装
}
/// 最小長を検証
fn min_length(s: &str, min: usize) -> Result<(), String> {
// TODO: 実装
}
/// 最大長を検証
fn max_length(s: &str, max: usize) -> Result<(), String> {
// TODO: 実装
}
/// メールアドレス形式を検証
fn is_email(s: &str) -> Result<(), &'static str> {
// TODO: 実装
// 簡易版:@を含むかチェック
}
/// 数値のみかを検証
fn is_numeric(s: &str) -> Result<(), &'static str> {
// TODO: 実装
}
/// 範囲内の数値かを検証
fn in_range(value: i32, min: i32, max: i32) -> Result<(), String> {
// TODO: 実装
}
}
fn validate_user_input(username: &str, email: &str, age: i32) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
if let Err(e) = Validator::min_length(username, 3) {
errors.push(e);
}
if let Err(e) = Validator::is_email(email) {
errors.push(e.to_string());
}
if let Err(e) = Validator::in_range(age, 0, 120) {
errors.push(e);
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
fn main() {
match validate_user_input("ab", "invalid", 150) {
Ok(_) => println!("検証成功"),
Err(errors) => {
println!("検証エラー:");
for error in errors {
println!(" - {}", error);
}
}
}
}
提出物:bonus2_validator/プロジェクト
ボーナス3:キャッシュシステム(10点)
参照を活用したキャッシュシステムを実装しなさい。
use std::collections::HashMap;
struct Cache<'a> {
data: HashMap<&'a str, String>,
}
impl<'a> Cache<'a> {
fn new() -> Self {
Cache {
data: HashMap::new(),
}
}
/// キャッシュに保存
fn set(&mut self, key: &'a str, value: String) {
// TODO: 実装
}
/// キャッシュから取得
fn get(&self, key: &str) -> Option<&str> {
// TODO: 実装
}
/// キャッシュから削除
fn remove(&mut self, key: &str) -> Option<String> {
// TODO: 実装
}
/// キャッシュをクリア
fn clear(&mut self) {
// TODO: 実装
}
/// キーが存在するか
fn contains(&self, key: &str) -> bool {
// TODO: 実装
}
}
fn main() {
let mut cache = Cache::new();
cache.set("user:1", String::from("Alice"));
cache.set("user:2", String::from("Bob"));
if let Some(name) = cache.get("user:1") {
println!("ユーザー1: {}", name);
}
cache.remove("user:2");
println!("user:2が存在するか: {}", cache.contains("user:2"));
}
提出物:bonus3_cache/プロジェクト
ボーナス4:JSONパーサー(簡易版)(10点)
参照を活用したJSONパーサーを実装しなさい。
#[derive(Debug, PartialEq)]
enum JsonValue<'a> {
Null,
Bool(bool),
Number(f64),
String(&'a str),
Array(Vec<JsonValue<'a>>),
Object(Vec<(&'a str, JsonValue<'a>)>),
}
fn parse_string(input: &str) -> Option<&str> {
// TODO: 簡易実装
// ダブルクォートで囲まれた部分を返す
}
fn parse_number(input: &str) -> Option<f64> {
// TODO: 実装
}
fn parse_bool(input: &str) -> Option<bool> {
// TODO: 実装
}
fn main() {
// テスト
let json = r#"{"name": "Alice", "age": 30, "active": true}"#;
// TODO: パース実装
}
提出物:bonus4_json_parser/プロジェクト
ボーナス5:参照カウンター(10点)
参照カウントを手動で実装しなさい。
use std::cell::Cell;
struct RefCounter<T> {
value: T,
count: Cell<usize>,
}
impl<T> RefCounter<T> {
fn new(value: T) -> Self {
RefCounter {
value,
count: Cell::new(1),
}
}
fn increment(&self) {
// TODO: 実装
}
fn decrement(&self) -> usize {
// TODO: 実装
}
fn count(&self) -> usize {
// TODO: 実装
}
fn get(&self) -> &T {
// TODO: 実装
}
}
fn main() {
let counter = RefCounter::new(String::from("hello"));
counter.increment();
counter.increment();
println!("参照カウント: {}", counter.count());
counter.decrement();
println!("参照カウント: {}", counter.count());
println!("値: {}", counter.get());
}
提出物:bonus5_ref_counter/プロジェクト
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:借用ルール | 25点 | ルールの正確な理解 |
| 問題2:参照関数 | 25点 | 適切な参照の使用 |
| 問題3:文字列処理 | 25点 | スライスの活用 |
| 問題4:配列処理 | 25点 | 可変/不変参照の使い分け |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:エディタ | 10点 | 実用性と設計 |
| ボーナス2:バリデーション | 10点 | エラー処理の質 |
| ボーナス3:キャッシュ | 10点 | ライフタイムの理解 |
| ボーナス4:パーサー | 10点 | スライスの高度な利用 |
| ボーナス5:参照カウンター | 10点 | 内部可変性の理解 |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-foundations-05/
├── problem1_analysis.md
├── problem2_references.rs
├── problem3_string_utils.rs
├── problem4_array_utils.rs
└── bonus/
├── bonus1_text_editor/
├── bonus2_validator/
├── bonus3_cache/
├── bonus4_json_parser/
└── bonus5_ref_counter/
提出期限
---
ヒント
問題2のヒント
first_word関数:
fn first_word(s: &str) -> &str {
for (i, &byte) in s.as_bytes().iter().enumerate() {
if byte == b' ' {
return &s[..i];
}
}
&s[..]
}
問題3のヒント
パリンドローム判定:
fn is_palindrome(s: &str) -> bool {
let cleaned: String = s.chars()
.filter(|c| c.is_alphanumeric())
.map(|c| c.to_ascii_lowercase())
.collect();
cleaned == cleaned.chars().rev().collect::<String>()
}
問題4のヒント
配列の逆順:
fn reverse_array(arr: &mut [i32]) {
arr.reverse(); // 組み込みメソッド
// または手動で
let len = arr.len();
for i in 0..len/2 {
arr.swap(i, len - 1 - i);
}
}
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
- [ ] 不変参照と可変参照の違い
- [ ] 借用の3つのルール
- [ ] 参照のスコープ(NLL)
- [ ] ダングリング参照の防止
- [ ] スライスの使い方
- [ ] &strとStringの使い分け
- [ ] 関数引数での参照の選択
次の章では、ライフタイムを学びます。参照の有効期間を明示的に指定する方法です。