Add a Rust FuzzTest codelab

PiperOrigin-RevId: 986733795
diff --git a/rust/codelab/BUILD b/rust/codelab/BUILD
new file mode 100644
index 0000000..3d0d03d
--- /dev/null
+++ b/rust/codelab/BUILD
@@ -0,0 +1,40 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# The Escaping Library example for the Rust FuzzTest Codelab.
+
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+package(default_visibility = ["//visibility:private"])
+
+licenses(["notice"])
+
+rust_library(
+    name = "escaping",
+    srcs = ["escaping.rs"],
+    crate_root = "escaping.rs",
+    edition = "2024",
+)
+
+rust_test(
+    name = "escaping_test",
+    srcs = ["escaping_test.rs"],
+    crate_root = "escaping_test.rs",
+    edition = "2024",
+    deps = [
+        ":escaping",
+        "@com_google_fuzztest//rust:fuzztest",
+        "@crate_index//:googletest",
+    ],
+)
diff --git a/rust/codelab/escaping.rs b/rust/codelab/escaping.rs
new file mode 100644
index 0000000..260cdb0
--- /dev/null
+++ b/rust/codelab/escaping.rs
@@ -0,0 +1,61 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! A tiny escaping library used by the Rust FuzzTest codelab.
+//!
+//! It converts special characters in a byte string into C-style escape
+//! sequences and back.
+//!
+//! The library intentionally contains two bugs that the codelab's fuzz tests
+//! are meant to discover, so please don't "fix" them.
+
+/// Returns `input` with special characters replaced by C-style escape
+/// sequences.
+pub fn escape(input: &[u8]) -> Vec<u8> {
+    let mut result = Vec::with_capacity(input.len());
+    for &byte in input {
+        match byte {
+            b'\n' => result.extend_from_slice(br"\n"),
+            b'\r' => result.extend_from_slice(br"\r"),
+            b'\t' => result.extend_from_slice(br"\t"),
+            b'\\' => result.extend_from_slice(br"\\"),
+            _ => result.push(byte),
+        }
+    }
+    result
+}
+
+/// Returns `input` with C-style escape sequences replaced by the characters
+/// they represent.
+///
+/// Unrecognized escape sequences are dropped.
+pub fn unescape(input: &[u8]) -> Vec<u8> {
+    let mut result = Vec::with_capacity(input.len());
+    let mut i = 0;
+    while i < input.len() {
+        if input[i] == b'\\' {
+            i += 1;
+            match input[i] {
+                b'n' => result.push(b'\n'),
+                b't' => result.push(b'\t'),
+                b'\\' => result.push(b'\\'),
+                _ => {}
+            }
+        } else {
+            result.push(input[i]);
+        }
+        i += 1;
+    }
+    result
+}
diff --git a/rust/codelab/escaping_test.rs b/rust/codelab/escaping_test.rs
new file mode 100644
index 0000000..94ea211
--- /dev/null
+++ b/rust/codelab/escaping_test.rs
@@ -0,0 +1,71 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use escaping::escape;
+use escaping::unescape;
+use googletest::prelude::*;
+
+#[gtest]
+fn escaping_empty_input_returns_empty_output() {
+    expect_that!(escape(b""), eq(b""));
+}
+
+#[gtest]
+fn unescaping_empty_input_returns_empty_output() {
+    expect_that!(unescape(b""), eq(b""));
+}
+
+#[gtest]
+fn escaping_plain_input_is_returned_as_is() {
+    expect_that!(escape(b"plain text"), eq(b"plain text"));
+}
+
+#[gtest]
+fn unescaping_plain_input_is_returned_as_is() {
+    expect_that!(unescape(b"plain text"), eq(b"plain text"));
+}
+
+#[gtest]
+fn escaping_replaces_special_characters() {
+    expect_that!(escape(b"two\nlines"), eq(br"two\nlines"));
+    expect_that!(escape(b"back\\slash"), eq(br"back\\slash"));
+}
+
+#[gtest]
+fn unescaping_replaces_escape_sequences() {
+    expect_that!(unescape(br"two\nlines"), eq(b"two\nlines"));
+    expect_that!(unescape(br"back\\slash"), eq(b"back\\slash"));
+}
+
+// Uncomment the following imports and fuzz tests as you work through the
+// codelab.
+//
+// use fuzztest::domains::arbitrary::Arbitrary;
+// use fuzztest::domains::containers::VecOf;
+// use fuzztest::fuzztest;
+
+// #[fuzztest(input = VecOf::new(Arbitrary::<u8>::default()))]
+// fn unescaping_escaped_input_gives_original(input: Vec<u8>) {
+//     assert_eq!(unescape(&escape(&input)), input);
+// }
+
+// #[fuzztest(input = VecOf::new(Arbitrary::<u8>::default()))]
+// fn escaping_never_panics(input: Vec<u8>) {
+//     escape(&input);
+// }
+
+// #[fuzztest(input = VecOf::new(Arbitrary::<u8>::default()))]
+// fn unescaping_never_panics(input: Vec<u8>) {
+//     unescape(&input);
+// }