課題7: 型システムの実践
マンダトリー要件
問題1:型の理解(20点)
以下の質問に答えなさい。
// 1-1: 以下の各変数の型を答えなさい
fn question_1() {
let a = 42;
let b = 3.14;
let c = true;
let d = 'A';
let e = [1, 2, 3];
let f = (1, "hello", true);
let g = vec![1, 2, 3];
let h = String::from("hello");
let i = "world";
}
// 1-2: 以下のコードはコンパイルできるか?理由を説明しなさい
fn question_2() {
let x = 5;
let y = x;
println!("{}, {}", x, y);
}
fn question_3() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}, {}", s1, s2);
}
// 1-3: 各型のメモリサイズを答えなさい(64bit環境)
// i8, i32, i64, f32, f64, bool, char
// &i32, String, Vec<i32>
// 1-4: 以下の型変換は成功するか?
fn question_4() {
let x: i32 = 1000;
let y: u8 = x as u8; // yの値は?
let a: f64 = 3.9;
let b: i32 = a as i32; // bの値は?
}
各質問について:
- 型を特定
- 理由を説明
- エラーの場合、修正方法を提示
提出物:problem1_type_analysis.md
問題2:型変換ユーティリティ(20点)
様々な型変換関数を実装しなさい。
use std::num::ParseIntError;
// 文字列を整数に変換
fn parse_int(s: &str) -> Result<i32, ParseIntError> {
// TODO: 実装
}
// 整数を文字列に変換
fn int_to_string(n: i32) -> String {
// TODO: 実装
}
// 摂氏を華氏に変換
fn celsius_to_fahrenheit(celsius: f64) -> f64 {
// TODO: 実装
// 式: (celsius * 9/5) + 32
}
// 華氏を摂氏に変換
fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 {
// TODO: 実装
}
// バイト配列を文字列に変換
fn bytes_to_string(bytes: Vec<u8>) -> Result<String, std::string::FromUtf8Error> {
// TODO: 実装
}
// 文字列をバイト配列に変換
fn string_to_bytes(s: String) -> Vec<u8> {
// TODO: 実装
}
// 16進数文字列を整数に変換
fn hex_to_int(hex: &str) -> Result<i32, ParseIntError> {
// TODO: 実装
// 例: "FF" -> 255
}
// 2進数文字列を整数に変換
fn binary_to_int(binary: &str) -> Result<i32, ParseIntError> {
// TODO: 実装
// 例: "1010" -> 10
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_int() {
assert_eq!(parse_int("42").unwrap(), 42);
assert_eq!(parse_int("-10").unwrap(), -10);
assert!(parse_int("abc").is_err());
}
#[test]
fn test_int_to_string() {
assert_eq!(int_to_string(42), "42");
assert_eq!(int_to_string(-10), "-10");
}
#[test]
fn test_temperature_conversion() {
assert_eq!(celsius_to_fahrenheit(0.0), 32.0);
assert_eq!(celsius_to_fahrenheit(100.0), 212.0);
assert_eq!(fahrenheit_to_celsius(32.0), 0.0);
}
#[test]
fn test_bytes_conversion() {
let bytes = vec![72, 101, 108, 108, 111];
assert_eq!(bytes_to_string(bytes).unwrap(), "Hello");
let s = String::from("World");
assert_eq!(string_to_bytes(s), vec![87, 111, 114, 108, 100]);
}
#[test]
fn test_hex_to_int() {
assert_eq!(hex_to_int("FF").unwrap(), 255);
assert_eq!(hex_to_int("10").unwrap(), 16);
}
#[test]
fn test_binary_to_int() {
assert_eq!(binary_to_int("1010").unwrap(), 10);
assert_eq!(binary_to_int("1111").unwrap(), 15);
}
}
提出物:problem2_type_conversions.rs
問題3:型安全なラッパー(20点)
newtype パターンを使った型安全なラッパーを実装しなさい。
// ユーザーID
struct UserId(u64);
impl UserId {
fn new(id: u64) -> Self {
// TODO: 実装
}
fn value(&self) -> u64 {
// TODO: 実装
}
}
// メールアドレス
struct Email(String);
impl Email {
fn new(email: String) -> Result<Self, &'static str> {
// TODO: 実装
// 簡易バリデーション:@を含むかチェック
}
fn value(&self) -> &str {
// TODO: 実装
}
fn domain(&self) -> Option<&str> {
// TODO: 実装
// @以降を返す
}
}
// 年齢
struct Age(u8);
impl Age {
fn new(age: u8) -> Result<Self, &'static str> {
// TODO: 実装
// 0-120の範囲チェック
}
fn value(&self) -> u8 {
// TODO: 実装
}
fn is_adult(&self) -> bool {
// TODO: 実装
// 18歳以上かチェック
}
}
// 金額(セント単位)
struct Money(i64);
impl Money {
fn new(cents: i64) -> Self {
// TODO: 実装
}
fn from_dollars(dollars: f64) -> Self {
// TODO: 実装
}
fn cents(&self) -> i64 {
// TODO: 実装
}
fn dollars(&self) -> f64 {
// TODO: 実装
}
fn add(&self, other: &Money) -> Money {
// TODO: 実装
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_id() {
let id = UserId::new(123);
assert_eq!(id.value(), 123);
}
#[test]
fn test_email() {
let email = Email::new("user@example.com".to_string()).unwrap();
assert_eq!(email.value(), "user@example.com");
assert_eq!(email.domain(), Some("example.com"));
assert!(Email::new("invalid".to_string()).is_err());
}
#[test]
fn test_age() {
let age = Age::new(25).unwrap();
assert_eq!(age.value(), 25);
assert!(age.is_adult());
let child = Age::new(10).unwrap();
assert!(!child.is_adult());
assert!(Age::new(150).is_err());
}
#[test]
fn test_money() {
let m1 = Money::new(100);
assert_eq!(m1.cents(), 100);
assert_eq!(m1.dollars(), 1.0);
let m2 = Money::from_dollars(2.5);
assert_eq!(m2.cents(), 250);
let total = m1.add(&m2);
assert_eq!(total.cents(), 350);
}
}
提出物:problem3_type_wrappers.rs
問題4:データ構造の実装(20点)
様々な型を使ったデータ構造を実装しなさい。
// 座標
#[derive(Debug, PartialEq)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Self {
// TODO: 実装
}
fn distance_from_origin(&self) -> f64 {
// TODO: 実装
// √(x² + y²)
}
fn distance_to(&self, other: &Point) -> f64 {
// TODO: 実装
}
}
// 長方形
#[derive(Debug)]
struct Rectangle {
top_left: Point,
width: f64,
height: f64,
}
impl Rectangle {
fn new(top_left: Point, width: f64, height: f64) -> Self {
// TODO: 実装
}
fn area(&self) -> f64 {
// TODO: 実装
}
fn perimeter(&self) -> f64 {
// TODO: 実装
}
fn contains(&self, point: &Point) -> bool {
// TODO: 実装
}
}
// RGB色
#[derive(Debug, PartialEq)]
struct Color {
r: u8,
g: u8,
b: u8,
}
impl Color {
fn new(r: u8, g: u8, b: u8) -> Self {
// TODO: 実装
}
fn from_hex(hex: &str) -> Result<Self, &'static str> {
// TODO: 実装
// 例: "FF0000" -> Color { r: 255, g: 0, b: 0 }
}
fn to_hex(&self) -> String {
// TODO: 実装
}
fn brightness(&self) -> f64 {
// TODO: 実装
// (r + g + b) / 3
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point() {
let p1 = Point::new(3.0, 4.0);
assert_eq!(p1.distance_from_origin(), 5.0);
let p2 = Point::new(0.0, 0.0);
assert_eq!(p1.distance_to(&p2), 5.0);
}
#[test]
fn test_rectangle() {
let rect = Rectangle::new(Point::new(0.0, 0.0), 10.0, 5.0);
assert_eq!(rect.area(), 50.0);
assert_eq!(rect.perimeter(), 30.0);
assert!(rect.contains(&Point::new(5.0, 2.5)));
assert!(!rect.contains(&Point::new(15.0, 2.5)));
}
#[test]
fn test_color() {
let red = Color::new(255, 0, 0);
assert_eq!(red.to_hex(), "FF0000");
assert_eq!(red.brightness(), 85.0);
let blue = Color::from_hex("0000FF").unwrap();
assert_eq!(blue, Color::new(0, 0, 255));
}
}
提出物:problem4_data_structures.rs
---
ボーナス課題
> ボーナス: 以下はオプションです。マンダトリー要件を完了してから挑戦してください。
ボーナス1:単位変換ライブラリ(5点)
様々な単位変換を実装しなさい。
// 長さ
struct Length {
meters: f64,
}
impl Length {
fn from_meters(meters: f64) -> Self {
// TODO: 実装
}
fn from_kilometers(km: f64) -> Self {
// TODO: 実装
}
fn from_miles(miles: f64) -> Self {
// TODO: 実装
}
fn to_meters(&self) -> f64 {
// TODO: 実装
}
fn to_kilometers(&self) -> f64 {
// TODO: 実装
}
fn to_miles(&self) -> f64 {
// TODO: 実装
}
}
// 重さ
struct Weight {
grams: f64,
}
impl Weight {
fn from_grams(grams: f64) -> Self {
// TODO: 実装
}
fn from_kilograms(kg: f64) -> Self {
// TODO: 実装
}
fn from_pounds(lbs: f64) -> Self {
// TODO: 実装
// 1ポンド = 453.592グラム
}
fn to_grams(&self) -> f64 {
// TODO: 実装
}
fn to_kilograms(&self) -> f64 {
// TODO: 実装
}
fn to_pounds(&self) -> f64 {
// TODO: 実装
}
}
提出物:bonus1_unit_conversions/プロジェクト
ボーナス2:日付・時刻の型(5点)
安全な日付・時刻型を実装しなさい。
#[derive(Debug, PartialEq)]
struct Date {
year: u16,
month: u8,
day: u8,
}
impl Date {
fn new(year: u16, month: u8, day: u8) -> Result<Self, &'static str> {
// TODO: バリデーション付き実装
}
fn is_leap_year(year: u16) -> bool {
// TODO: 実装
}
fn days_in_month(&self) -> u8 {
// TODO: 実装
}
fn next_day(&self) -> Date {
// TODO: 実装
}
fn format(&self) -> String {
// TODO: 実装
// YYYY-MM-DD形式
}
}
#[derive(Debug, PartialEq)]
struct Time {
hour: u8,
minute: u8,
second: u8,
}
impl Time {
fn new(hour: u8, minute: u8, second: u8) -> Result<Self, &'static str> {
// TODO: バリデーション付き実装
}
fn total_seconds(&self) -> u32 {
// TODO: 実装
}
fn add_seconds(&self, seconds: u32) -> Time {
// TODO: 実装
}
}
提出物:bonus2_datetime/プロジェクト
ボーナス3:2D ベクトル演算(5点)
2Dベクトルの演算を実装しなさい。
#[derive(Debug, Clone, Copy, PartialEq)]
struct Vector2D {
x: f64,
y: f64,
}
impl Vector2D {
fn new(x: f64, y: f64) -> Self {
// TODO: 実装
}
fn magnitude(&self) -> f64 {
// TODO: 実装
}
fn normalize(&self) -> Vector2D {
// TODO: 実装
}
fn dot(&self, other: &Vector2D) -> f64 {
// TODO: 内積
}
fn add(&self, other: &Vector2D) -> Vector2D {
// TODO: ベクトル加算
}
fn subtract(&self, other: &Vector2D) -> Vector2D {
// TODO: ベクトル減算
}
fn scale(&self, scalar: f64) -> Vector2D {
// TODO: スカラー倍
}
}
提出物:bonus3_vector2d/プロジェクト
ボーナス4:有理数型(5点)
分数を扱う有理数型を実装しなさい。
#[derive(Debug, PartialEq)]
struct Rational {
numerator: i64,
denominator: i64,
}
impl Rational {
fn new(numerator: i64, denominator: i64) -> Result<Self, &'static str> {
// TODO: 実装(分母0チェック、約分)
}
fn add(&self, other: &Rational) -> Rational {
// TODO: 実装
}
fn multiply(&self, other: &Rational) -> Rational {
// TODO: 実装
}
fn to_f64(&self) -> f64 {
// TODO: 実装
}
fn gcd(a: i64, b: i64) -> i64 {
// TODO: 最大公約数
}
fn reduce(&mut self) {
// TODO: 約分
}
}
提出物:bonus4_rational/プロジェクト
ボーナス5:型安全なビルダーパターン(5点)
ビルダーパターンを実装しなさい。
struct User {
name: String,
email: String,
age: Option<u8>,
address: Option<String>,
}
struct UserBuilder {
name: Option<String>,
email: Option<String>,
age: Option<u8>,
address: Option<String>,
}
impl UserBuilder {
fn new() -> Self {
// TODO: 実装
}
fn name(mut self, name: String) -> Self {
// TODO: 実装
}
fn email(mut self, email: String) -> Self {
// TODO: 実装
}
fn age(mut self, age: u8) -> Self {
// TODO: 実装
}
fn address(mut self, address: String) -> Self {
// TODO: 実装
}
fn build(self) -> Result<User, &'static str> {
// TODO: 実装(nameとemailは必須)
}
}
fn main() {
let user = UserBuilder::new()
.name("Alice".to_string())
.email("alice@example.com".to_string())
.age(30)
.build()
.unwrap();
println!("{:?}", user);
}
提出物:bonus5_builder_pattern/プロジェクト
---
評価基準
マンダトリー部分(80点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| 問題1:型の理解 | 20点 | 型システムの正確な理解 |
| 問題2:型変換 | 20点 | 適切な変換処理 |
| 問題3:型安全性 | 20点 | newtype パターンの活用 |
| 問題4:データ構造 | 20点 | 複合型の実装 |
ボーナス部分(20点)
| 項目 | 配点 | 評価ポイント |
|---|---|---|
| ボーナス1:単位変換 | 5点 | 変換ロジック |
| ボーナス2:日付・時刻 | 5点 | バリデーション |
| ボーナス3:ベクトル演算 | 5点 | 数学的正確性 |
| ボーナス4:有理数 | 5点 | 約分処理 |
| ボーナス5:ビルダー | 5点 | 設計の質 |
注: ボーナスは最大20点まで加算されます。
---
提出方法
ファイル構成
rust-foundations-07/
├── problem1_type_analysis.md
├── problem2_type_conversions.rs
├── problem3_type_wrappers.rs
├── problem4_data_structures.rs
└── bonus/
├── bonus1_unit_conversions/
├── bonus2_datetime/
├── bonus3_vector2d/
├── bonus4_rational/
└── bonus5_builder_pattern/
提出期限
---
ヒント
問題2のヒント
hex_to_int実装:
fn hex_to_int(hex: &str) -> Result<i32, ParseIntError> {
i32::from_str_radix(hex, 16)
}
問題3のヒント
Email実装:
impl Email {
fn new(email: String) -> Result<Self, &'static str> {
if email.contains('@') {
Ok(Email(email))
} else {
Err("Invalid email format")
}
}
fn domain(&self) -> Option<&str> {
self.0.split('@').nth(1)
}
}
問題4のヒント
Color::from_hex実装:
fn from_hex(hex: &str) -> Result<Self, &'static str> {
if hex.len() != 6 {
return Err("Invalid hex length");
}
let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Invalid hex")?;
let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| "Invalid hex")?;
let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Invalid hex")?;
Ok(Color { r, g, b })
}
---
学習の確認
この課題を通じて、以下を理解できたか確認してください:
- [ ] プリミティブ型の種類とサイズ
- [ ] 複合型(タプル、配列)の使い方
- [ ] 型推論の仕組み
- [ ] asキャストの使い方と注意点
- [ ] From/Into トレイトの実装
- [ ] newtype パターンの活用
- [ ] Copy と Clone の違い
次の章では、制御構造を学びます。条件分岐、ループ、パターンマッチングについて理解を深めます。