0Pricing
Learn Rust Coding · درس

نمط Newtype

غلّف الأنواع لتعزيز الأمان والوضوح

نمط Newtype درس مجاني في Learn Rust Coding على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Learn Rust Coding، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

What Is a Newtype?

A newtype is a single-field tuple struct that wraps an existing type to give it a distinct identity. struct Meters(f64) is a brand new type even though it holds a plain f64.

The wrapper has zero runtime cost but lets the compiler enforce meaning that a raw primitive cannot.

struct Meters(f64);
struct Seconds(f64);

Preventing Unit Mix-Ups

Raw primitives are easy to confuse. If both a distance and a time are f64, nothing stops you swapping them in a call.

Wrapping each in its own newtype makes such mistakes a compile error instead of a silent bug.

fn speed(d: Meters, t: Seconds) -> f64 {
    d.0 / t.0
}
// speed(Seconds(2.0), Meters(10.0)) -> compile error

Accessing the Inner Value

You reach the wrapped value through tuple index .0. Many newtypes also expose a method or implement From for ergonomic conversions.

struct UserId(u64);
impl UserId {
    fn value(&self) -> u64 { self.0 }
}
fn main() {
    let id = UserId(42);
    println!("id = {}", id.value());
}

Encapsulating Invariants

Make the inner field private and validate in a constructor. Then any value of the newtype is guaranteed valid, so downstream code never re-checks.

pub struct Email(String);
impl Email {
    pub fn new(s: &str) -> Option<Email> {
        if s.contains('@') {
            Some(Email(s.to_string()))
        } else {
            None
        }
    }
}

The Orphan Rule

Rust forbids implementing a foreign trait for a foreign type. You cannot write impl Display for Vec<T> because you own neither the trait nor the type.

This rule keeps trait coherence sound across crates, but it can block useful impls.

Newtypes Bypass the Orphan Rule

Because the newtype is defined in your crate, you now own a local type and may implement any trait for it. This is the standard workaround for the orphan rule.

use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

A Runnable Wrapper Display

Here the wrapper from the previous scene is put to work. We own Wrapper, so implementing Display for it is allowed and the program prints the joined list.

use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}
fn main() {
    let w = Wrapper(vec!["a".into(), "b".into()]);
    println!("{}", w);
}

Restricting the API Surface

Wrapping a powerful type lets you expose only a safe subset. A NonEmptyVec can hide mutating methods that would let it become empty, preserving its invariant.

pub struct NonEmptyVec<T>(Vec<T>);
impl<T> NonEmptyVec<T> {
    pub fn new(first: T) -> Self {
        NonEmptyVec(vec![first])
    }
    pub fn first(&self) -> &T {
        &self.0[0]
    }
}

Zero-Cost Abstraction

A newtype with one field has the same memory layout as the wrapped value. The compiler optimizes the wrapper away, so safety here is genuinely free at runtime.

Adding #[repr(transparent)] guarantees identical layout, which matters for FFI.

#[repr(transparent)]
struct Celsius(f64);

Deriving Traits on Newtypes

Newtypes often derive standard traits so they behave like the inner value where appropriate. Deriving keeps them ergonomic for keys, comparisons, and debug output.

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct ProductId(u32);
fn main() {
    let a = ProductId(7);
    let b = a.clone();
    println!("{:?} == {:?}: {}", a, b, a == b);
}

Newtype Versus Type Alias

Do not confuse a newtype with a type alias. type Meters = f64 is just a name; it is still an f64 and offers no extra safety.

A newtype struct Meters(f64) is a genuinely distinct type the compiler can keep separate.

type Km = f64;       // alias: interchangeable with f64
struct Mi(f64);      // newtype: distinct from f64

Quick Check

Decide why a newtype helps where a type alias does not.

Recap

The newtype pattern wraps an existing type in a one-field tuple struct to gain a distinct identity at zero runtime cost. It prevents value mix-ups, encapsulates invariants behind a private field, and sidesteps the orphan rule so you can implement foreign traits.

Unlike a type alias, a newtype is a real, separate type.

الأسئلة الشائعة

هل درس «نمط Newtype» مجاني؟

نعم — نص درس «نمط Newtype» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Learn Rust Coding، انتقل إلى CoddyKit PRO. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.

ماذا ستتعلم في «نمط Newtype»؟

غلّف الأنواع لتعزيز الأمان والوضوح تتمرن على Learn Rust Coding مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Learn Rust Coding؟

لا تُشترط خبرة سابقة. Learn Rust Coding على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «نمط Newtype»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Learn Rust Coding هذا؟

نعم. كل درس في Learn Rust Coding يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. نمط Builder
  2. نمط Newtype
  3. Builders بحالة النوع
  4. Deref وسهولة استخدام الأغلفة
← العودة إلى Learn Rust Coding