ExamplesBy LevelBy TopicLearning Paths
089 Intermediate

089 — Bob

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "089 — Bob" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Implement a conversational responder named Bob. Key difference from OCaml: | Aspect | Rust | OCaml |

Tutorial

The Problem

Implement a conversational responder named Bob. Bob replies based on the characteristics of the input: silence gets "Fine. Be that way!", yelling gets "Whoa, chill out!", a question gets "Sure.", a yelled question gets "Calm down, I know what I'm doing!", and anything else gets "Whatever." Compare both a tuple-match and an if/else implementation.

🎯 Learning Outcomes

  • • Use .trim(), .ends_with('?'), and .chars().any(…) for string inspection
  • • Compute is_yelling by checking both letter presence and full uppercase equality
  • • Pattern match on a tuple of booleans (is_silence, is_yelling, is_question) for clear dispatch
  • • Return &'static str for response literals — no allocation needed
  • • Map Rust's method-based string operations to OCaml's String module functions
  • • Understand the priority ordering in the match arms (silence checked first)
  • Code Example

    #![allow(clippy::all)]
    /// Bob — String Pattern Matching
    ///
    /// Ownership: Input is borrowed &str. Responses are &'static str (no allocation).
    
    fn is_question(s: &str) -> bool {
        s.trim().ends_with('?')
    }
    
    fn is_yelling(s: &str) -> bool {
        let has_letter = s.chars().any(|c| c.is_alphabetic());
        has_letter && s == s.to_uppercase()
    }
    
    fn is_silence(s: &str) -> bool {
        s.trim().is_empty()
    }
    
    pub fn response_for(s: &str) -> &'static str {
        match (is_silence(s), is_yelling(s), is_question(s)) {
            (true, _, _) => "Fine. Be that way!",
            (_, true, true) => "Calm down, I know what I'm doing!",
            (_, true, false) => "Whoa, chill out!",
            (_, false, true) => "Sure.",
            _ => "Whatever.",
        }
    }
    
    /// Version 2: Using if-else chain (more readable for some)
    pub fn response_for_v2(s: &str) -> &'static str {
        let trimmed = s.trim();
        if trimmed.is_empty() {
            "Fine. Be that way!"
        } else if is_yelling(s) && is_question(s) {
            "Calm down, I know what I'm doing!"
        } else if is_yelling(s) {
            "Whoa, chill out!"
        } else if is_question(s) {
            "Sure."
        } else {
            "Whatever."
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_yelling() {
            assert_eq!(response_for("WATCH OUT!"), "Whoa, chill out!");
        }
    
        #[test]
        fn test_question() {
            assert_eq!(response_for("Does this work?"), "Sure.");
        }
    
        #[test]
        fn test_yelling_question() {
            assert_eq!(
                response_for("WHAT IS THIS?"),
                "Calm down, I know what I'm doing!"
            );
        }
    
        #[test]
        fn test_silence() {
            assert_eq!(response_for("   "), "Fine. Be that way!");
        }
    
        #[test]
        fn test_normal() {
            assert_eq!(response_for("Hi"), "Whatever.");
        }
    
        #[test]
        fn test_v2_matches() {
            for s in &[
                "WATCH OUT!",
                "Does this work?",
                "WHAT IS THIS?",
                "   ",
                "Hi",
            ] {
                assert_eq!(response_for(s), response_for_v2(s));
            }
        }
    }

    Key Differences

    AspectRustOCaml
    Uppercase checks == s.to_uppercase()String.uppercase_ascii s = s
    Letter presence.chars().any(c.is_alphabetic())Seq.exists (fun c -> …)
    Trim.trim()String.trim
    Last char.ends_with('?')String.get s (len - 1) = '?'
    Dispatchmatch (bool, bool, bool)match …, …, … tuple
    Response type&'static strstring

    The tuple match is a clean alternative to nested if/else chains. Exhaustive checking ensures no case is forgotten — adding a new boolean flag forces updating every match arm. The &'static str return type for constant responses is a best practice: avoid allocating when the string is already in the binary.

    OCaml Approach

    OCaml checks is_question by indexing the last character of the trimmed string. is_yelling uses Seq.exists on String.to_seq for letter presence and String.uppercase_ascii for comparison. response_for pattern-matches on the same triple. The logic is identical; OCaml's string API is more functional (String.to_seq, Seq.exists) whereas Rust uses method calls on &str.

    Full Source

    #![allow(clippy::all)]
    /// Bob — String Pattern Matching
    ///
    /// Ownership: Input is borrowed &str. Responses are &'static str (no allocation).
    
    fn is_question(s: &str) -> bool {
        s.trim().ends_with('?')
    }
    
    fn is_yelling(s: &str) -> bool {
        let has_letter = s.chars().any(|c| c.is_alphabetic());
        has_letter && s == s.to_uppercase()
    }
    
    fn is_silence(s: &str) -> bool {
        s.trim().is_empty()
    }
    
    pub fn response_for(s: &str) -> &'static str {
        match (is_silence(s), is_yelling(s), is_question(s)) {
            (true, _, _) => "Fine. Be that way!",
            (_, true, true) => "Calm down, I know what I'm doing!",
            (_, true, false) => "Whoa, chill out!",
            (_, false, true) => "Sure.",
            _ => "Whatever.",
        }
    }
    
    /// Version 2: Using if-else chain (more readable for some)
    pub fn response_for_v2(s: &str) -> &'static str {
        let trimmed = s.trim();
        if trimmed.is_empty() {
            "Fine. Be that way!"
        } else if is_yelling(s) && is_question(s) {
            "Calm down, I know what I'm doing!"
        } else if is_yelling(s) {
            "Whoa, chill out!"
        } else if is_question(s) {
            "Sure."
        } else {
            "Whatever."
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_yelling() {
            assert_eq!(response_for("WATCH OUT!"), "Whoa, chill out!");
        }
    
        #[test]
        fn test_question() {
            assert_eq!(response_for("Does this work?"), "Sure.");
        }
    
        #[test]
        fn test_yelling_question() {
            assert_eq!(
                response_for("WHAT IS THIS?"),
                "Calm down, I know what I'm doing!"
            );
        }
    
        #[test]
        fn test_silence() {
            assert_eq!(response_for("   "), "Fine. Be that way!");
        }
    
        #[test]
        fn test_normal() {
            assert_eq!(response_for("Hi"), "Whatever.");
        }
    
        #[test]
        fn test_v2_matches() {
            for s in &[
                "WATCH OUT!",
                "Does this work?",
                "WHAT IS THIS?",
                "   ",
                "Hi",
            ] {
                assert_eq!(response_for(s), response_for_v2(s));
            }
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_yelling() {
            assert_eq!(response_for("WATCH OUT!"), "Whoa, chill out!");
        }
    
        #[test]
        fn test_question() {
            assert_eq!(response_for("Does this work?"), "Sure.");
        }
    
        #[test]
        fn test_yelling_question() {
            assert_eq!(
                response_for("WHAT IS THIS?"),
                "Calm down, I know what I'm doing!"
            );
        }
    
        #[test]
        fn test_silence() {
            assert_eq!(response_for("   "), "Fine. Be that way!");
        }
    
        #[test]
        fn test_normal() {
            assert_eq!(response_for("Hi"), "Whatever.");
        }
    
        #[test]
        fn test_v2_matches() {
            for s in &[
                "WATCH OUT!",
                "Does this work?",
                "WHAT IS THIS?",
                "   ",
                "Hi",
            ] {
                assert_eq!(response_for(s), response_for_v2(s));
            }
        }
    }

    Deep Comparison

    Bob — Comparison

    Core Insight

    Bob demonstrates tuple pattern matching on computed boolean conditions. The approach is identical in both languages — compute predicates, match on the tuple. String operations differ slightly in ergonomics.

    OCaml Approach

  • String.trim, String.uppercase_ascii for string ops
  • String.to_seq |> Seq.exists for character testing
  • • Manual last-character check: String.get s (String.length s - 1)
  • • Tuple match: match a, b, c with | true, _, _ -> ...
  • Rust Approach

  • .trim(), .to_uppercase() — method syntax
  • .chars().any(|c| c.is_alphabetic()) for character testing
  • .ends_with('?') — dedicated method
  • • Tuple match: match (a, b, c) { (true, _, _) => ... }
  • Comparison Table

    AspectOCamlRust
    TrimString.trim.trim()
    UppercaseString.uppercase_ascii.to_uppercase()
    Ends withManual char check.ends_with()
    Has letterSeq.exists.chars().any()
    Tuple matchmatch a, b, c withmatch (a, b, c)
    Result typestring&'static str

    Learner Notes

  • • Rust's .ends_with() is more ergonomic than OCaml's manual index check
  • &'static str for constant strings avoids allocation
  • • Both languages handle tuple matching the same way — very elegant pattern
  • • Rust's .is_alphabetic() handles Unicode; OCaml's manual check doesn't
  • Exercises

  • Add a is_polite(s: &str) -> bool predicate that checks for "please" and integrate it into the response logic.
  • Implement a version that returns String (allocated) to allow parameterised responses (e.g. "Sure, {name}.").
  • Handle multi-line input: split on newlines, classify each line, and return a composite response.
  • Add a counter to track how many times Bob has been asked a question without is_yelling.
  • In OCaml, extend response_for to also detect silence made of only punctuation (e.g. "...") and treat it as silence.
  • Open Source Repos