Skip to main content
added 108 characters in body
Source Link
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

#[derive(Debug)]
pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
} 

#[derive(Debug)]
pub struct Isbn([u8; 10]);

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        for digit in self.0 {
            if digit == 10 {
                write!(f, "X")?;
            } else {
                write!(f, "{digit}")?;
            }
        }

        Ok(())
    }
}

impl From<[u8; 9]> for Isbn {
    fn from(value: [u8; 9]) -> Self {
        Self([
            value[0],
            value[1],
            value[2],
            value[3],
            value[4],
            value[5],
            value[6],
            value[7],
            value[8],
            calculate_checksum(&value),
        ])
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        let array: [u8; 9] = match digits.try_into() {
            Ok(array) => array,
            Err(error) => return Err(Error::InvalidLength(error.len())),
        };

        Ok(Self::from(array))
    }
}

fn calculate_checksum(digits: &[u8; 9]) -> u8 {
    let dot_product: u32 = digits
        .iter()
        .zip(FACTORS)
        .map(|(&x, y)| u32::from(x) * u32::from(y))
        .sum();

    for checksum in 0..=10u8 {
        if (dot_product + u32::from(checksum)) % 11 == 0 {
            return checksum;
        }
    }

    0
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

#[derive(Debug)]
pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
} 

#[derive(Debug)]
pub struct Isbn([u8; 9]);

impl Isbn {
    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .0
            .iter()
            .zip(FACTORS)
            .map(|(&x, y)| u32::from(x) * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        for digit in self.0 {
            write!(f, "{digit}")?;
        }

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl From<[u8; 9]> for Isbn {
    fn from(value: [u8; 9]) -> Self {
        Self(value)
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        let array: [u8; 9] = match digits.try_into() {
            Ok(array) => array,
            Err(error) => return Err(Error::InvalidLength(error.len())),
        };

        Ok(Self::from(array))
    }
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

#[derive(Debug)]
pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
} 

#[derive(Debug)]
pub struct Isbn(u32);

impl Isbn {
    #[must_use]
    pub fn digits(&self) -> String {
        self.0.to_string()
    }

    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .digits()
            .chars()
            .map(|c| c.to_digit(10).unwrap_or_else(|| unreachable!()))
            .zip(FACTORS)
            .map(|(x, y)| x * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }

    fn from_digits(digits: &[u8]) -> Result<Self, Error> {
        if digits.len() != 9 {
            return Err(Error::InvalidLength(digits.len()));
        }

        Ok(Self(
            digits
                .iter()
                .rev()
                .enumerate()
                .map(|(exponent, digit)| {
                    u32::from(*digit)
                        * 10u32.pow(u32::try_from(exponent).unwrap_or_else(|_| unreachable!()))
                })
                .sum(),
        ))
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.digits())?;

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        Self::from_digits(digits.as_slice())
    }
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
}

pub struct Isbn([u8; 10]);

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        for digit in self.0 {
            if digit == 10 {
                write!(f, "X")?;
            } else {
                write!(f, "{digit}")?;
            }
        }

        Ok(())
    }
}

impl From<[u8; 9]> for Isbn {
    fn from(value: [u8; 9]) -> Self {
        Self([
            value[0],
            value[1],
            value[2],
            value[3],
            value[4],
            value[5],
            value[6],
            value[7],
            value[8],
            calculate_checksum(&value),
        ])
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        let array: [u8; 9] = match digits.try_into() {
            Ok(array) => array,
            Err(error) => return Err(Error::InvalidLength(error.len())),
        };

        Ok(Self::from(array))
    }
}

fn calculate_checksum(digits: &[u8; 9]) -> u8 {
    let dot_product: u32 = digits
        .iter()
        .zip(FACTORS)
        .map(|(&x, y)| u32::from(x) * u32::from(y))
        .sum();

    for checksum in 0..=10u8 {
        if (dot_product + u32::from(checksum)) % 11 == 0 {
            return checksum;
        }
    }

    0
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
}

pub struct Isbn([u8; 9]);

impl Isbn {
    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .0
            .iter()
            .zip(FACTORS)
            .map(|(&x, y)| u32::from(x) * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        for digit in self.0 {
            write!(f, "{digit}")?;
        }

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl From<[u8; 9]> for Isbn {
    fn from(value: [u8; 9]) -> Self {
        Self(value)
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        let array: [u8; 9] = match digits.try_into() {
            Ok(array) => array,
            Err(error) => return Err(Error::InvalidLength(error.len())),
        };

        Ok(Self::from(array))
    }
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
}

pub struct Isbn(u32);

impl Isbn {
    #[must_use]
    pub fn digits(&self) -> String {
        self.0.to_string()
    }

    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .digits()
            .chars()
            .map(|c| c.to_digit(10).unwrap_or_else(|| unreachable!()))
            .zip(FACTORS)
            .map(|(x, y)| x * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }

    fn from_digits(digits: &[u8]) -> Result<Self, Error> {
        if digits.len() != 9 {
            return Err(Error::InvalidLength(digits.len()));
        }

        Ok(Self(
            digits
                .iter()
                .rev()
                .enumerate()
                .map(|(exponent, digit)| {
                    u32::from(*digit)
                        * 10u32.pow(u32::try_from(exponent).unwrap_or_else(|_| unreachable!()))
                })
                .sum(),
        ))
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.digits())?;

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        Self::from_digits(digits.as_slice())
    }
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

#[derive(Debug)]
pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
} 

#[derive(Debug)]
pub struct Isbn([u8; 10]);

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        for digit in self.0 {
            if digit == 10 {
                write!(f, "X")?;
            } else {
                write!(f, "{digit}")?;
            }
        }

        Ok(())
    }
}

impl From<[u8; 9]> for Isbn {
    fn from(value: [u8; 9]) -> Self {
        Self([
            value[0],
            value[1],
            value[2],
            value[3],
            value[4],
            value[5],
            value[6],
            value[7],
            value[8],
            calculate_checksum(&value),
        ])
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        let array: [u8; 9] = match digits.try_into() {
            Ok(array) => array,
            Err(error) => return Err(Error::InvalidLength(error.len())),
        };

        Ok(Self::from(array))
    }
}

fn calculate_checksum(digits: &[u8; 9]) -> u8 {
    let dot_product: u32 = digits
        .iter()
        .zip(FACTORS)
        .map(|(&x, y)| u32::from(x) * u32::from(y))
        .sum();

    for checksum in 0..=10u8 {
        if (dot_product + u32::from(checksum)) % 11 == 0 {
            return checksum;
        }
    }

    0
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

#[derive(Debug)]
pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
} 

#[derive(Debug)]
pub struct Isbn([u8; 9]);

impl Isbn {
    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .0
            .iter()
            .zip(FACTORS)
            .map(|(&x, y)| u32::from(x) * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        for digit in self.0 {
            write!(f, "{digit}")?;
        }

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl From<[u8; 9]> for Isbn {
    fn from(value: [u8; 9]) -> Self {
        Self(value)
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        let array: [u8; 9] = match digits.try_into() {
            Ok(array) => array,
            Err(error) => return Err(Error::InvalidLength(error.len())),
        };

        Ok(Self::from(array))
    }
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

#[derive(Debug)]
pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
} 

#[derive(Debug)]
pub struct Isbn(u32);

impl Isbn {
    #[must_use]
    pub fn digits(&self) -> String {
        self.0.to_string()
    }

    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .digits()
            .chars()
            .map(|c| c.to_digit(10).unwrap_or_else(|| unreachable!()))
            .zip(FACTORS)
            .map(|(x, y)| x * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }

    fn from_digits(digits: &[u8]) -> Result<Self, Error> {
        if digits.len() != 9 {
            return Err(Error::InvalidLength(digits.len()));
        }

        Ok(Self(
            digits
                .iter()
                .rev()
                .enumerate()
                .map(|(exponent, digit)| {
                    u32::from(*digit)
                        * 10u32.pow(u32::try_from(exponent).unwrap_or_else(|_| unreachable!()))
                })
                .sum(),
        ))
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.digits())?;

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        Self::from_digits(digits.as_slice())
    }
}
added 2618 characters in body
Source Link
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
}

pub struct Isbn(u32);

impl Isbn {
    #[must_use]
    pub fn digits(&self) -> String {
        self.0.to_string()
    }

    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .digits()
            .chars()
            .map(|c| c.to_digit(10).unwrap_or_else(|| unreachable!()))
            .zip(FACTORS)
            .map(|(x, y)| x * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }
}

impl Display for Isbn {
    fn fmtfrom_digits(&self, fdigits: &mut Formatter<'_>&[u8]) -> std::fmt::ResultResult<Self, Error> {
        write!(f, "{}",if self.digits.len())?;

        match!= self.checksum()9 {
            10 =>return write!Err(f, "X"Error::InvalidLength(digits.len(),));
        }

    checksum => write!(f, "{checksum}"), Ok(Self(
        }
    }
}
digits
impl FromStr for Isbn {
    type Err = Error;

    fn from_str.iter(s: &str) 
 -> Result<Self, Self::Err> {
        let mut digits = Vec::new.rev();

        for c in s     .charsenumerate() {
            if let Some(digit) = c.to_digitmap(10).and_then|(|digit|exponent, u8::try_from(digit).ok())| {
                digits.push(digit);
     u32::from(*digit)
       } else {
                return* Err10u32.pow(Erroru32::InvalidDigittry_from(cexponent).unwrap_or_else(|_| unreachable!());)
                })
        }

        Self::try_from(digits.as_slicesum(),
        ))
    }
}

impl TryFrom<&[u8]>Display for Isbn {
    type Error = Error;

    fn try_fromfmt(digits&self, f: &[u8]&mut Formatter<'_>) -> Result<Self, Selfstd::Error>fmt::Result {
        ifwrite!(f, digits"{}", self.lendigits())?;

 != 9      match self.checksum() {
            return10 Err(Error::InvalidLength(digits.len=> write!())f, "X");,
        }

      checksum => Ok(Selfwrite!(f, "{checksum}"),
        }
    digits}
}

impl FromStr for Isbn {
    type Err = Error;

    fn .iterfrom_str(s: &str)
  -> Result<Self, Self::Err> {
        let mut digits = .revVec::new();
      
        for c in s.enumeratechars() {
            if let Some(digit) = c.mapto_digit(|10).and_then(exponent,|digit| u8::try_from(digit)|.ok()) {
                digits.push(digit);
    u32::from(*digit)
        } else {
              * 10u32.pow return Err(u32Error::try_from(exponent).unwrap_or_else(|_| unreachable!InvalidDigit()c));
                })
        }

        Self::from_digits(digits.sumas_slice(),
        ))
    }
}

Here I implemented from_digits() as a private method of Isbn and not via a TryFrom<&[u8]> implementation, since it would need additional checks to guarantee that each digit is in {0..=9} if it was exposed publicly where this prerequisite is not guaranteed.

use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
}

pub struct Isbn(u32);

impl Isbn {
    #[must_use]
    pub fn digits(&self) -> String {
        self.0.to_string()
    }

    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .digits()
            .chars()
            .map(|c| c.to_digit(10).unwrap_or_else(|| unreachable!()))
            .zip(FACTORS)
            .map(|(x, y)| x * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.digits())?;

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        Self::try_from(digits.as_slice())
    }
}

impl TryFrom<&[u8]> for Isbn {
    type Error = Error;

    fn try_from(digits: &[u8]) -> Result<Self, Self::Error> {
        if digits.len() != 9 {
            return Err(Error::InvalidLength(digits.len()));
        }

        Ok(Self(
            digits
                .iter()
                 .rev()
                .enumerate()
                .map(|(exponent, digit)| {
                    u32::from(*digit)
                        * 10u32.pow(u32::try_from(exponent).unwrap_or_else(|_| unreachable!()))
                })
                .sum(),
        ))
    }
}
use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
}

pub struct Isbn(u32);

impl Isbn {
    #[must_use]
    pub fn digits(&self) -> String {
        self.0.to_string()
    }

    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .digits()
            .chars()
            .map(|c| c.to_digit(10).unwrap_or_else(|| unreachable!()))
            .zip(FACTORS)
            .map(|(x, y)| x * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }

    fn from_digits(digits: &[u8]) -> Result<Self, Error> {
        if digits.len() != 9 {
            return Err(Error::InvalidLength(digits.len()));
        }

        Ok(Self(
            digits
                .iter() 
                .rev()
                .enumerate()
                .map(|(exponent, digit)| {
                    u32::from(*digit)
                        * 10u32.pow(u32::try_from(exponent).unwrap_or_else(|_| unreachable!()))
                })
                .sum(),
        ))
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.digits())?;

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();
 
        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        Self::from_digits(digits.as_slice())
    }
}

Here I implemented from_digits() as a private method of Isbn and not via a TryFrom<&[u8]> implementation, since it would need additional checks to guarantee that each digit is in {0..=9} if it was exposed publicly where this prerequisite is not guaranteed.

added 2618 characters in body
Source Link

You can even optimize the storage space of the Isbn further by just storing its numerical value, which maxes out at 999999999 and thus fits into a u32:

src/lib.rs

use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
}

pub struct Isbn(u32);

impl Isbn {
    #[must_use]
    pub fn digits(&self) -> String {
        self.0.to_string()
    }

    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .digits()
            .chars()
            .map(|c| c.to_digit(10).unwrap_or_else(|| unreachable!()))
            .zip(FACTORS)
            .map(|(x, y)| x * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.digits())?;

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        Self::try_from(digits.as_slice())
    }
}

impl TryFrom<&[u8]> for Isbn {
    type Error = Error;

    fn try_from(digits: &[u8]) -> Result<Self, Self::Error> {
        if digits.len() != 9 {
            return Err(Error::InvalidLength(digits.len()));
        }

        Ok(Self(
            digits
                .iter()
                .rev()
                .enumerate()
                .map(|(exponent, digit)| {
                    u32::from(*digit)
                        * 10u32.pow(u32::try_from(exponent).unwrap_or_else(|_| unreachable!()))
                })
                .sum(),
        ))
    }
}

You can even optimize the storage space of the Isbn further by just storing its numerical value, which maxes out at 999999999 and thus fits into a u32:

src/lib.rs

use std::fmt::{Display, Formatter};
use std::str::FromStr;

const FACTORS: [u8; 9] = [10, 9, 8, 7, 6, 5, 4, 3, 2];

pub enum Error {
    InvalidDigit(char),
    InvalidLength(usize),
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidDigit(chr) => write!(f, "Invalid digit: {chr}"),
            Self::InvalidLength(len) => write!(f, "Invalid length: {len}"),
        }
    }
}

pub struct Isbn(u32);

impl Isbn {
    #[must_use]
    pub fn digits(&self) -> String {
        self.0.to_string()
    }

    #[must_use]
    pub fn checksum(&self) -> u8 {
        let dot_product: u32 = self
            .digits()
            .chars()
            .map(|c| c.to_digit(10).unwrap_or_else(|| unreachable!()))
            .zip(FACTORS)
            .map(|(x, y)| x * u32::from(y))
            .sum();

        for checksum in 0..=10u8 {
            if (dot_product + u32::from(checksum)) % 11 == 0 {
                return checksum;
            }
        }

        0
    }
}

impl Display for Isbn {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.digits())?;

        match self.checksum() {
            10 => write!(f, "X"),
            checksum => write!(f, "{checksum}"),
        }
    }
}

impl FromStr for Isbn {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut digits = Vec::new();

        for c in s.chars() {
            if let Some(digit) = c.to_digit(10).and_then(|digit| u8::try_from(digit).ok()) {
                digits.push(digit);
            } else {
                return Err(Error::InvalidDigit(c));
            }
        }

        Self::try_from(digits.as_slice())
    }
}

impl TryFrom<&[u8]> for Isbn {
    type Error = Error;

    fn try_from(digits: &[u8]) -> Result<Self, Self::Error> {
        if digits.len() != 9 {
            return Err(Error::InvalidLength(digits.len()));
        }

        Ok(Self(
            digits
                .iter()
                .rev()
                .enumerate()
                .map(|(exponent, digit)| {
                    u32::from(*digit)
                        * 10u32.pow(u32::try_from(exponent).unwrap_or_else(|_| unreachable!()))
                })
                .sum(),
        ))
    }
}
added 2 characters in body
Source Link
Loading
added 2109 characters in body
Source Link
Loading
added 2109 characters in body
Source Link
Loading
added 31 characters in body
Source Link
Loading
Fix overflow bug
Source Link
Loading
Source Link
Loading