rust-ownership - 解答

実装コード

所有権関連関数

// src/ownership.rs

/// 所有権を受け取り、変換して返す
pub fn process_owned(mut data: String) -> String {
    data.make_ascii_uppercase();
    data.push_str("_PROCESSED");
    data
}

/// 所有権を消費して新しい値を生成
pub fn consume_and_create(a: String, b: String) -> String {
    format!("{}-{}", a, b)
}

/// 条件に基づいて所有権を返すか消費
pub fn conditional_ownership(data: String, keep: bool) -> Option<String> {
    if keep {
        Some(data)
    } else {
        // data は drop される
        None
    }
}

借用関連関数

// src/borrowing.rs

/// 不変借用で読み取り
pub fn process_borrowed(data: &str) -> String {
    data.to_uppercase()
}

/// 不変借用で検索
pub fn find_substring(haystack: &str, needle: &str) -> Option<usize> {
    haystack.find(needle)
}

/// 複数の不変借用
pub fn compare_strings(a: &str, b: &str) -> std::cmp::Ordering {
    a.len().cmp(&b.len())
}

/// 可変借用で変更
pub fn modify_in_place(data: &mut String) {
    *data = data.to_uppercase();
    data.push_str("!");
}

/// 可変借用でフィルタリング
pub fn retain_alphabetic(data: &mut String) {
    data.retain(|c| c.is_alphabetic());
}

/// スライスを返す(借用を返す)
pub fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

ライフタイム構造体

// src/lifetime.rs

/// 参照を保持する構造体
pub struct TextAnalyzer<'a> {
    text: &'a str,
}

impl<'a> TextAnalyzer<'a> {
    pub fn new(text: &'a str) -> Self {
        TextAnalyzer { text }
    }

    pub fn word_count(&self) -> usize {
        self.text.split_whitespace().count()
    }

    pub fn char_count(&self) -> usize {
        self.text.chars().count()
    }

    /// テキスト内のパターンを検索し、マッチした部分を返す
    pub fn find_pattern(&self, pattern: &str) -> Vec<&'a str> {
        self.text
            .match_indices(pattern)
            .map(|(i, _)| &self.text[i..i + pattern.len()])
            .collect()
    }

    /// 行のイテレータを返す
    pub fn lines(&self) -> impl Iterator<Item = &'a str> {
        self.text.lines()
    }
}

/// 複数ライフタイムを持つ構造体
pub struct Merger<'a, 'b> {
    left: &'a str,
    right: &'b str,
}

impl<'a, 'b> Merger<'a, 'b> {
    pub fn new(left: &'a str, right: &'b str) -> Self {
        Merger { left, right }
    }

    /// 短い方のライフタイムを持つ結果を返す
    pub fn merge(&self) -> String {
        format!("{}{}", self.left, self.right)
    }

    /// 左側の参照を返す('a ライフタイム)
    pub fn left(&self) -> &'a str {
        self.left
    }

    /// 右側の参照を返す('b ライフタイム)
    pub fn right(&self) -> &'b str {
        self.right
    }
}

/// 入力と同じライフタイムの参照を返す
pub fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

/// 静的ライフタイムの参照を返す
pub fn static_str() -> &'static str {
    "This string lives forever"
}

ボーナス: カスタムイテレータ

/// 単語を返すイテレータ
pub struct Words<'a> {
    text: &'a str,
    position: usize,
}

impl<'a> Words<'a> {
    pub fn new(text: &'a str) -> Self {
        Words { text, position: 0 }
    }
}

impl<'a> Iterator for Words<'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<Self::Item> {
        // 空白をスキップ
        while self.position < self.text.len()
            && self.text.as_bytes()[self.position].is_ascii_whitespace()
        {
            self.position += 1;
        }

        if self.position >= self.text.len() {
            return None;
        }

        let start = self.position;

        // 単語の終わりを探す
        while self.position < self.text.len()
            && !self.text.as_bytes()[self.position].is_ascii_whitespace()
        {
            self.position += 1;
        }

        Some(&self.text[start..self.position])
    }
}

テストコード

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_process_owned() {
        let s = String::from("hello");
        let result = process_owned(s);
        assert_eq!(result, "HELLO_PROCESSED");
    }

    #[test]
    fn test_process_borrowed() {
        let s = String::from("hello");
        let result = process_borrowed(&s);
        assert_eq!(result, "HELLO");
        assert_eq!(s, "hello"); // 元の値は変更されていない
    }

    #[test]
    fn test_modify_in_place() {
        let mut s = String::from("hello");
        modify_in_place(&mut s);
        assert_eq!(s, "HELLO!");
    }

    #[test]
    fn test_text_analyzer() {
        let text = String::from("hello world");
        let analyzer = TextAnalyzer::new(&text);
        assert_eq!(analyzer.word_count(), 2);
        assert_eq!(analyzer.char_count(), 11);
    }

    #[test]
    fn test_merger() {
        let left = String::from("Hello, ");
        let right = String::from("World!");
        let merger = Merger::new(&left, &right);
        assert_eq!(merger.merge(), "Hello, World!");
    }

    #[test]
    fn test_longest() {
        let s1 = String::from("short");
        let s2 = String::from("much longer");
        let result = longest(&s1, &s2);
        assert_eq!(result, "much longer");
    }

    #[test]
    fn test_words_iterator() {
        let text = "hello world rust";
        let words: Vec<_> = Words::new(text).collect();
        assert_eq!(words, vec!["hello", "world", "rust"]);
    }
}