ExamplesBy LevelBy TopicLearning Paths
265 Fundamental

Caesar Cipher — Functional Encryption

String Processing

Tutorial Video

Text description (accessibility)

This video demonstrates the "Caesar Cipher — Functional Encryption" functional Rust example. Difficulty level: Fundamental. Key concepts covered: String Processing. Implement a Caesar cipher that shifts each letter by a fixed number of positions in the alphabet. Key difference from OCaml: 1. **Char arithmetic:** OCaml uses `Char.code`/`Char.chr`; Rust casts with `as u8`/`as char`

Tutorial

The Problem

Implement a Caesar cipher that shifts each letter by a fixed number of positions in the alphabet. Non-letter characters pass through unchanged. Provide both encryption and decryption.

🎯 Learning Outcomes

  • • Character arithmetic in Rust using as u8 and as char conversions
  • • Range patterns in match arms ('a'..='z')
  • • String transformation with .chars().map().collect()
  • • Function composition: decryption as a shifted encryption
  • 🦀 The Rust Way

    Rust uses as u8 / as char for character arithmetic and range patterns 'a'..='z' in match arms. The iterator chain .chars().map().collect() replaces String.map. Decryption uses the same shift-reversal technique.

    Code Example

    fn shift_char(n: u8, c: char) -> char {
        match c {
            'a'..='z' => ((c as u8 - b'a' + n) % 26 + b'a') as char,
            'A'..='Z' => ((c as u8 - b'A' + n) % 26 + b'A') as char,
            _ => c,
        }
    }
    
    pub fn caesar(n: u8, s: &str) -> String {
        s.chars().map(|c| shift_char(n, c)).collect()
    }
    
    pub fn decrypt(n: u8, s: &str) -> String {
        caesar(26 - (n % 26), s)
    }

    Key Differences

  • Char arithmetic: OCaml uses Char.code/Char.chr; Rust casts with as u8/as char
  • Pattern matching: OCaml uses if/else on char comparisons; Rust uses range patterns 'a'..='z'
  • String mapping: OCaml's String.map applies a function per char; Rust uses .chars().map().collect()
  • Partial application: OCaml's let decrypt n = caesar (26 - n) is more concise; Rust needs a full function definition
  • OCaml Approach

    OCaml uses Char.code and Char.chr for character arithmetic and String.map to apply the shift function to every character. decrypt is elegantly defined as caesar (26 - n) using partial application.

    Full Source

    #![allow(clippy::all)]
    //! Caesar Cipher — Functional Encryption
    //!
    //! OCaml: `let caesar n s = String.map (shift_char n) s`
    //! Rust: `fn caesar(n: u8, s: &str) -> String` using `.chars().map().collect()`
    //!
    //! A Caesar cipher shifts each letter by a fixed number of positions
    //! in the alphabet, wrapping around. Non-letter characters pass through unchanged.
    
    //! Shifts a single character by `n` positions.
    //!
    //! OCaml: pattern matches on char ranges with `>=` and `<=`.
    //! Rust: uses range patterns in match arms.
    fn shift_char(n: u8, c: char) -> char {
        match c {
            'a'..='z' => {
                let shifted = (c as u8 - b'a' + n) % 26 + b'a';
                shifted as char
            }
            'A'..='Z' => {
                let shifted = (c as u8 - b'A' + n) % 26 + b'A';
                shifted as char
            }
            _ => c,
        }
    }
    
    /// Encrypts a string using Caesar cipher with shift `n`.
    ///
    /// OCaml: `let caesar n s = String.map (shift_char n) s`
    /// Rust uses iterator chain: `.chars().map().collect()`.
    pub fn caesar(n: u8, s: &str) -> String {
        s.chars().map(|c| shift_char(n, c)).collect()
    }
    
    /// Decrypts by shifting in the opposite direction.
    ///
    /// OCaml: `let decrypt n = caesar (26 - n)`
    /// Rust: same idea — shift by `26 - n`.
    pub fn decrypt(n: u8, s: &str) -> String {
        caesar(26 - (n % 26), s)
    }
    
    /// ROT13: the classic self-inverse Caesar cipher (shift by 13).
    /// Applying it twice returns the original.
    pub fn rot13(s: &str) -> String {
        caesar(13, s)
    }
    
    /// Iterator-based approach: returns a lazy iterator of shifted chars.
    pub fn caesar_iter(n: u8, s: &str) -> impl Iterator<Item = char> + '_ {
        s.chars().map(move |c| shift_char(n, c))
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_basic_encryption() {
            assert_eq!(caesar(13, "Hello World"), "Uryyb Jbeyq");
        }
    
        #[test]
        fn test_decryption_reverses() {
            let msg = "Hello World";
            let encrypted = caesar(13, msg);
            let decrypted = decrypt(13, &encrypted);
            assert_eq!(decrypted, msg);
        }
    
        #[test]
        fn test_rot13_self_inverse() {
            let msg = "The Quick Brown Fox";
            assert_eq!(rot13(&rot13(msg)), msg);
        }
    
        #[test]
        fn test_preserves_non_alpha() {
            assert_eq!(caesar(5, "Hello, World! 123"), "Mjqqt, Btwqi! 123");
        }
    
        #[test]
        fn test_zero_shift() {
            assert_eq!(caesar(0, "unchanged"), "unchanged");
        }
    
        #[test]
        fn test_full_rotation() {
            assert_eq!(caesar(26, "abc"), "abc");
        }
    
        #[test]
        fn test_wraparound() {
            assert_eq!(caesar(1, "xyz"), "yza");
            assert_eq!(caesar(1, "XYZ"), "YZA");
        }
    
        #[test]
        fn test_empty_string() {
            assert_eq!(caesar(5, ""), "");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_basic_encryption() {
            assert_eq!(caesar(13, "Hello World"), "Uryyb Jbeyq");
        }
    
        #[test]
        fn test_decryption_reverses() {
            let msg = "Hello World";
            let encrypted = caesar(13, msg);
            let decrypted = decrypt(13, &encrypted);
            assert_eq!(decrypted, msg);
        }
    
        #[test]
        fn test_rot13_self_inverse() {
            let msg = "The Quick Brown Fox";
            assert_eq!(rot13(&rot13(msg)), msg);
        }
    
        #[test]
        fn test_preserves_non_alpha() {
            assert_eq!(caesar(5, "Hello, World! 123"), "Mjqqt, Btwqi! 123");
        }
    
        #[test]
        fn test_zero_shift() {
            assert_eq!(caesar(0, "unchanged"), "unchanged");
        }
    
        #[test]
        fn test_full_rotation() {
            assert_eq!(caesar(26, "abc"), "abc");
        }
    
        #[test]
        fn test_wraparound() {
            assert_eq!(caesar(1, "xyz"), "yza");
            assert_eq!(caesar(1, "XYZ"), "YZA");
        }
    
        #[test]
        fn test_empty_string() {
            assert_eq!(caesar(5, ""), "");
        }
    }

    Deep Comparison

    OCaml vs Rust: Caesar Cipher — Functional Encryption

    Side-by-Side Code

    OCaml

    let shift_char n c =
      if c >= 'a' && c <= 'z' then
        Char.chr ((Char.code c - Char.code 'a' + n) mod 26 + Char.code 'a')
      else if c >= 'A' && c <= 'Z' then
        Char.chr ((Char.code c - Char.code 'A' + n) mod 26 + Char.code 'A')
      else c
    
    let caesar n s = String.map (shift_char n) s
    let decrypt n = caesar (26 - n)
    

    Rust (idiomatic)

    fn shift_char(n: u8, c: char) -> char {
        match c {
            'a'..='z' => ((c as u8 - b'a' + n) % 26 + b'a') as char,
            'A'..='Z' => ((c as u8 - b'A' + n) % 26 + b'A') as char,
            _ => c,
        }
    }
    
    pub fn caesar(n: u8, s: &str) -> String {
        s.chars().map(|c| shift_char(n, c)).collect()
    }
    
    pub fn decrypt(n: u8, s: &str) -> String {
        caesar(26 - (n % 26), s)
    }
    

    Rust (iterator-based — lazy)

    pub fn caesar_iter(n: u8, s: &str) -> impl Iterator<Item = char> + '_ {
        s.chars().map(move |c| shift_char(n, c))
    }
    

    Type Signatures

    ConceptOCamlRust
    Shift charval shift_char : int -> char -> charfn shift_char(n: u8, c: char) -> char
    Encryptval caesar : int -> string -> stringfn caesar(n: u8, s: &str) -> String
    Decryptval decrypt : int -> string -> stringfn decrypt(n: u8, s: &str) -> String
    Char typechar (8-bit)char (32-bit Unicode scalar)
    String typestring (byte sequence)&str (UTF-8 borrowed) / String (owned)

    Key Insights

  • Range patterns are elegant: Rust's 'a'..='z' in match arms is cleaner than OCaml's if c >= 'a' && c <= 'z' — pattern matching at its best
  • Byte literals: Rust's b'a' is equivalent to OCaml's Char.code 'a' — both give the numeric value of the character
  • String ownership: OCaml's String.map returns a new string (strings are mutable but map creates new); Rust borrows &str and returns owned String
  • Lazy iteration: Rust's impl Iterator approach delays computation; OCaml would need Seq for the same laziness
  • Type safety on shift: Rust uses u8 for the shift amount, preventing negative shifts at the type level; OCaml uses int which could be negative (handled by mod)
  • When to Use Each Style

    **Use eager collection (caesar) when:** you need the full encrypted string as a String for storage or further processing **Use lazy iteration (caesar_iter) when:** you're chaining with other transformations or writing to a stream character-by-character

    Exercises

  • Implement caesar_crack that performs brute-force decryption: try all 25 shifts and return the one whose output most closely matches English letter frequencies.
  • Extend the Caesar cipher to the Vigenère cipher: accept a keyword and apply a different shift for each position, cycling through the keyword letters.
  • Implement ROT13 as a special case of Caesar cipher with shift 13 and verify that applying it twice returns the original text; then generalize to an arbitrary involution cipher.
  • Open Source Repos