This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch branch-1.11
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/branch-1.11 by this push:
     new 38f5d559f AVRO-3839: [Rust] Replace lazy_static crate with 
std::sync::OnceLock (#2461)
38f5d559f is described below

commit 38f5d559fed54a6cc2bb756771a3492c9c408954
Author: Martin Grigorov <[email protected]>
AuthorDate: Mon Dec 11 16:00:45 2023 +0200

    AVRO-3839: [Rust] Replace lazy_static crate with std::sync::OnceLock (#2461)
    
    * AVRO-3839: [Rust] Replace lazy_static crate with std::sync::OnceLock
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    
    * AVRO-3839: [Rust] Bump minimal Rust version to 1.70.0
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    
    ---------
    
    Signed-off-by: Martin Tzvetanov Grigorov <[email protected]>
    (cherry picked from commit 92512b20ec906b9912751a689f4d26f852485839)
---
 .github/workflows/test-lang-rust-ci.yml     |   4 +-
 .github/workflows/test-lang-rust-clippy.yml |   2 +-
 lang/rust/Cargo.lock                        |   2 -
 lang/rust/Cargo.toml                        |   3 +-
 lang/rust/avro/Cargo.toml                   |   1 -
 lang/rust/avro/README.md                    |   2 +-
 lang/rust/avro/src/rabin.rs                 |  13 +-
 lang/rust/avro/src/schema.rs                |  48 +++--
 lang/rust/avro/tests/io.rs                  | 271 +++++++++++++++++++---------
 lang/rust/avro/tests/schema.rs              |  68 +++----
 lang/rust/avro_test_helper/Cargo.toml       |   1 -
 lang/rust/avro_test_helper/src/logger.rs    |  11 +-
 share/docker/Dockerfile                     |   2 +-
 13 files changed, 273 insertions(+), 155 deletions(-)

diff --git a/.github/workflows/test-lang-rust-ci.yml 
b/.github/workflows/test-lang-rust-ci.yml
index f6e0dcb94..ef07e9606 100644
--- a/.github/workflows/test-lang-rust-ci.yml
+++ b/.github/workflows/test-lang-rust-ci.yml
@@ -50,7 +50,7 @@ jobs:
           - 'stable'
           - 'beta'
           - 'nightly'
-          - '1.65.0'  # MSRV
+          - '1.70.0'  # MSRV
         target:
           - x86_64-unknown-linux-gnu
           - wasm32-unknown-unknown
@@ -273,4 +273,4 @@ jobs:
       - name: Build
         run: |
           set -x
-          ./build.sh test
\ No newline at end of file
+          ./build.sh test
diff --git a/.github/workflows/test-lang-rust-clippy.yml 
b/.github/workflows/test-lang-rust-clippy.yml
index e02878631..78b2d718d 100644
--- a/.github/workflows/test-lang-rust-clippy.yml
+++ b/.github/workflows/test-lang-rust-clippy.yml
@@ -47,7 +47,7 @@ jobs:
       matrix:
         rust:
           - 'stable'
-          - '1.65.0'  # MSRV
+          - '1.70.0'  # MSRV
     steps:
       - uses: actions/checkout@v4
       - uses: dtolnay/rust-toolchain@nightly
diff --git a/lang/rust/Cargo.lock b/lang/rust/Cargo.lock
index 9a8330ab5..30b408cf4 100644
--- a/lang/rust/Cargo.lock
+++ b/lang/rust/Cargo.lock
@@ -75,7 +75,6 @@ dependencies = [
  "criterion",
  "digest",
  "hex-literal",
- "lazy_static",
  "libflate",
  "log",
  "md-5",
@@ -120,7 +119,6 @@ dependencies = [
  "better-panic",
  "ctor",
  "env_logger",
- "lazy_static",
  "log",
  "ref_thread_local",
 ]
diff --git a/lang/rust/Cargo.toml b/lang/rust/Cargo.toml
index 0494d9c1e..23ee3249f 100644
--- a/lang/rust/Cargo.toml
+++ b/lang/rust/Cargo.toml
@@ -34,14 +34,13 @@ authors = ["Apache Avro team <[email protected]>"]
 license = "Apache-2.0"
 repository = "https://github.com/apache/avro";
 edition = "2021"
-rust-version = "1.65.0"
+rust-version = "1.70.0"
 keywords = ["avro", "data", "serialization"]
 categories = ["encoding"]
 documentation = "https://docs.rs/apache-avro";
 
 # dependencies used by more than one members
 [workspace.dependencies]
-lazy_static = { default-features = false, version = "1.4.0" }
 log = { default-features = false, version = "0.4.20" }
 serde = { default-features = false, version = "1.0.193", features = ["derive"] 
}
 serde_json = { default-features = false, version = "1.0.108", features = 
["std"] }
diff --git a/lang/rust/avro/Cargo.toml b/lang/rust/avro/Cargo.toml
index ac52435d8..6568388e0 100644
--- a/lang/rust/avro/Cargo.toml
+++ b/lang/rust/avro/Cargo.toml
@@ -59,7 +59,6 @@ bigdecimal = { default-features = false, version = "0.4.2", 
features = ["std"] }
 bzip2 = { default-features = false, version = "0.4.4", optional = true }
 crc32fast = { default-features = false, version = "1.3.2", optional = true }
 digest = { default-features = false, version = "0.10.7", features = 
["core-api"] }
-lazy_static = { workspace = true }
 libflate = { default-features = false, version = "2.0.0", features = ["std"] }
 log = { workspace = true }
 num-bigint = { default-features = false, version = "0.4.4" }
diff --git a/lang/rust/avro/README.md b/lang/rust/avro/README.md
index bc48e104f..4dc43aa97 100644
--- a/lang/rust/avro/README.md
+++ b/lang/rust/avro/README.md
@@ -656,7 +656,7 @@ assert_eq!(false, 
SchemaCompatibility::can_read(&writers_schema, &readers_schema
 
 ## Minimal supported Rust version
 
-1.65.0
+1.70.0
 
 ## License
 This project is licensed under [Apache License 
2.0](https://github.com/apache/avro/blob/master/LICENSE.txt).
diff --git a/lang/rust/avro/src/rabin.rs b/lang/rust/avro/src/rabin.rs
index fc63f8999..f860769eb 100644
--- a/lang/rust/avro/src/rabin.rs
+++ b/lang/rust/avro/src/rabin.rs
@@ -20,22 +20,23 @@ use digest::{
     consts::U8, core_api::OutputSizeUser, generic_array::GenericArray, 
FixedOutput,
     FixedOutputReset, HashMarker, Output, Reset, Update,
 };
-use lazy_static::lazy_static;
+use std::sync::OnceLock;
 
 const EMPTY: i64 = -4513414715797952619;
 
-lazy_static! {
-    static ref FPTABLE: [i64; 256] = {
+fn fp_table() -> &'static [i64; 256] {
+    static FPTABLE_ONCE: OnceLock<[i64; 256]> = OnceLock::new();
+    FPTABLE_ONCE.get_or_init(|| {
         let mut fp_table: [i64; 256] = [0; 256];
         for i in 0..256 {
             let mut fp = i;
             for _ in 0..8 {
                 fp = (fp as u64 >> 1) as i64 ^ (EMPTY & -(fp & 1));
             }
-            fp_table[i as usize] = fp
+            fp_table[i as usize] = fp;
         }
         fp_table
-    };
+    })
 }
 
 /// Implementation of the Rabin fingerprint algorithm using the Digest trait 
as described in 
[schema_fingerprints](https://avro.apache.org/docs/current/spec.html#schema_fingerprints).
@@ -94,7 +95,7 @@ impl Update for Rabin {
     fn update(&mut self, data: &[u8]) {
         for b in data {
             self.result = (self.result as u64 >> 8) as i64
-                ^ FPTABLE[((self.result ^ *b as i64) & 0xff) as usize];
+                ^ fp_table()[((self.result ^ *b as i64) & 0xff) as usize];
         }
     }
 }
diff --git a/lang/rust/avro/src/schema.rs b/lang/rust/avro/src/schema.rs
index d3015dcec..3f84daab7 100644
--- a/lang/rust/avro/src/schema.rs
+++ b/lang/rust/avro/src/schema.rs
@@ -18,7 +18,6 @@
 //! Logic for parsing and interacting with schemas in Avro format.
 use crate::{error::Error, types, util::MapHelper, AvroResult};
 use digest::Digest;
-use lazy_static::lazy_static;
 use regex_lite::Regex;
 use serde::{
     ser::{SerializeMap, SerializeSeq},
@@ -34,19 +33,36 @@ use std::{
     hash::Hash,
     io::Read,
     str::FromStr,
+    sync::OnceLock,
 };
 use strum_macros::{EnumDiscriminants, EnumString};
 
-lazy_static! {
-    static ref ENUM_SYMBOL_NAME_R: Regex = 
Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap();
+fn enum_symbol_name_r() -> &'static Regex {
+    static ENUM_SYMBOL_NAME_ONCE: OnceLock<Regex> = OnceLock::new();
+    ENUM_SYMBOL_NAME_ONCE.get_or_init(|| 
Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap())
+}
 
-    // An optional namespace (with optional dots) followed by a name without 
any dots in it.
-    static ref SCHEMA_NAME_R: Regex =
-        
Regex::new(r"^((?P<namespace>([A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*)?)\.)?(?P<name>[A-Za-z_][A-Za-z0-9_]*)$").unwrap();
+// An optional namespace (with optional dots) followed by a name without any 
dots in it.
+fn schema_name_r() -> &'static Regex {
+    static SCHEMA_NAME_ONCE: OnceLock<Regex> = OnceLock::new();
+    SCHEMA_NAME_ONCE.get_or_init(|| {
+        Regex::new(
+            
r"^((?P<namespace>([A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*)?)\.)?(?P<name>[A-Za-z_][A-Za-z0-9_]*)$",
+        )
+        .unwrap()
+    })
+}
 
-    static ref FIELD_NAME_R: Regex = 
Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap();
+fn field_name_r() -> &'static Regex {
+    static FIELD_NAME_ONCE: OnceLock<Regex> = OnceLock::new();
+    FIELD_NAME_ONCE.get_or_init(|| 
Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap())
+}
 
-    static ref NAMESPACE_R: Regex = 
Regex::new(r"^([A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*)?$").unwrap();
+fn namespace_r() -> &'static Regex {
+    static NAMESPACE_ONCE: OnceLock<Regex> = OnceLock::new();
+    NAMESPACE_ONCE.get_or_init(|| {
+        
Regex::new(r"^([A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*)?$").unwrap()
+    })
 }
 
 /// Represents an Avro schema fingerprint
@@ -252,9 +268,9 @@ impl Name {
     }
 
     fn get_name_and_namespace(name: &str) -> AvroResult<(String, Namespace)> {
-        let caps = SCHEMA_NAME_R
+        let caps = schema_name_r()
             .captures(name)
-            .ok_or_else(|| Error::InvalidSchemaName(name.to_string(), 
SCHEMA_NAME_R.as_str()))?;
+            .ok_or_else(|| Error::InvalidSchemaName(name.to_string(), 
schema_name_r().as_str()))?;
         Ok((
             caps["name"].to_string(),
             caps.name("namespace").map(|s| s.as_str().to_string()),
@@ -285,10 +301,10 @@ impl Name {
             .filter(|ns| !ns.is_empty());
 
         if let Some(ref ns) = namespace {
-            if !NAMESPACE_R.is_match(ns) {
+            if !namespace_r().is_match(ns) {
                 return Err(Error::InvalidNamespace(
                     ns.to_string(),
-                    NAMESPACE_R.as_str(),
+                    namespace_r().as_str(),
                 ));
             }
         }
@@ -657,7 +673,7 @@ impl RecordField {
     ) -> AvroResult<Self> {
         let name = field.name().ok_or(Error::GetNameFieldFromRecord)?;
 
-        if !FIELD_NAME_R.is_match(&name) {
+        if !field_name_r().is_match(&name) {
             return Err(Error::FieldName(name));
         }
 
@@ -1617,7 +1633,7 @@ impl Parser {
         let mut existing_symbols: HashSet<&String> = 
HashSet::with_capacity(symbols.len());
         for symbol in symbols.iter() {
             // Ensure enum symbol names match [A-Za-z_][A-Za-z0-9_]*
-            if !ENUM_SYMBOL_NAME_R.is_match(symbol) {
+            if !enum_symbol_name_r().is_match(symbol) {
                 return Err(Error::EnumSymbolName(symbol.to_string()));
             }
 
@@ -6191,7 +6207,7 @@ mod tests {
         let name = Name::new(full_name);
         assert!(name.is_err());
         let expected =
-            Error::InvalidSchemaName(full_name.to_string(), 
SCHEMA_NAME_R.as_str()).to_string();
+            Error::InvalidSchemaName(full_name.to_string(), 
schema_name_r().as_str()).to_string();
         let err = name.map_err(|e| e.to_string()).err().unwrap();
         assert_eq!(expected, err);
 
@@ -6199,7 +6215,7 @@ mod tests {
         let name = Name::new(full_name);
         assert!(name.is_err());
         let expected =
-            Error::InvalidSchemaName(full_name.to_string(), 
SCHEMA_NAME_R.as_str()).to_string();
+            Error::InvalidSchemaName(full_name.to_string(), 
schema_name_r().as_str()).to_string();
         let err = name.map_err(|e| e.to_string()).err().unwrap();
         assert_eq!(expected, err);
         Ok(())
diff --git a/lang/rust/avro/tests/io.rs b/lang/rust/avro/tests/io.rs
index ab3712893..03442ed7f 100644
--- a/lang/rust/avro/tests/io.rs
+++ b/lang/rust/avro/tests/io.rs
@@ -18,90 +18,189 @@
 //! Port of 
https://github.com/apache/avro/blob/release-1.9.1/lang/py/test/test_io.py
 use apache_avro::{from_avro_datum, to_avro_datum, types::Value, Error, Schema};
 use apache_avro_test_helper::TestResult;
-use lazy_static::lazy_static;
 use pretty_assertions::assert_eq;
-use std::io::Cursor;
+use std::{io::Cursor, sync::OnceLock};
 
-lazy_static! {
-    static ref SCHEMAS_TO_VALIDATE: Vec<(&'static str, Value)> = vec![
-        (r#""null""#, Value::Null),
-        (r#""boolean""#, Value::Boolean(true)),
-        (r#""string""#, Value::String("adsfasdf09809dsf-=adsf".to_string())),
-        (r#""bytes""#, Value::Bytes("12345abcd".to_string().into_bytes())),
-        (r#""int""#, Value::Int(1234)),
-        (r#""long""#, Value::Long(1234)),
-        (r#""float""#, Value::Float(1234.0)),
-        (r#""double""#, Value::Double(1234.0)),
-        (r#"{"type": "fixed", "name": "Test", "size": 1}"#, Value::Fixed(1, 
vec![b'B'])),
-        (r#"{"type": "enum", "name": "Test", "symbols": ["A", "B"]}"#, 
Value::Enum(1, "B".to_string())),
-        (r#"{"type": "array", "items": "long"}"#, 
Value::Array(vec![Value::Long(1), Value::Long(3), Value::Long(2)])),
-        (r#"{"type": "map", "values": "long"}"#, Value::Map([("a".to_string(), 
Value::Long(1i64)), ("b".to_string(), Value::Long(3i64)), ("c".to_string(), 
Value::Long(2i64))].iter().cloned().collect())),
-        (r#"["string", "null", "long"]"#, Value::Union(1, 
Box::new(Value::Null))),
-        (r#"{"type": "record", "name": "Test", "fields": [{"name": "f", 
"type": "long"}]}"#, Value::Record(vec![("f".to_string(), Value::Long(1))]))
-    ];
-
-    static ref BINARY_ENCODINGS: Vec<(i64, Vec<u8>)> = vec![
-        (0, vec![0x00]),
-        (-1, vec![0x01]),
-        (1, vec![0x02]),
-        (-2, vec![0x03]),
-        (2, vec![0x04]),
-        (-64, vec![0x7f]),
-        (64, vec![0x80, 0x01]),
-        (8192, vec![0x80, 0x80, 0x01]),
-        (-8193, vec![0x81, 0x80, 0x01]),
-    ];
+fn schemas_to_validate() -> &'static Vec<(&'static str, Value)> {
+    static SCHEMAS_TO_VALIDATE_ONCE: OnceLock<Vec<(&'static str, Value)>> = 
OnceLock::new();
+    SCHEMAS_TO_VALIDATE_ONCE.get_or_init(|| {
+        vec![
+            (r#""null""#, Value::Null),
+            (r#""boolean""#, Value::Boolean(true)),
+            (
+                r#""string""#,
+                Value::String("adsfasdf09809dsf-=adsf".to_string()),
+            ),
+            (
+                r#""bytes""#,
+                Value::Bytes("12345abcd".to_string().into_bytes()),
+            ),
+            (r#""int""#, Value::Int(1234)),
+            (r#""long""#, Value::Long(1234)),
+            (r#""float""#, Value::Float(1234.0)),
+            (r#""double""#, Value::Double(1234.0)),
+            (
+                r#"{"type": "fixed", "name": "Test", "size": 1}"#,
+                Value::Fixed(1, vec![b'B']),
+            ),
+            (
+                r#"{"type": "enum", "name": "Test", "symbols": ["A", "B"]}"#,
+                Value::Enum(1, "B".to_string()),
+            ),
+            (
+                r#"{"type": "array", "items": "long"}"#,
+                Value::Array(vec![Value::Long(1), Value::Long(3), 
Value::Long(2)]),
+            ),
+            (
+                r#"{"type": "map", "values": "long"}"#,
+                Value::Map(
+                    [
+                        ("a".to_string(), Value::Long(1i64)),
+                        ("b".to_string(), Value::Long(3i64)),
+                        ("c".to_string(), Value::Long(2i64)),
+                    ]
+                    .iter()
+                    .cloned()
+                    .collect(),
+                ),
+            ),
+            (
+                r#"["string", "null", "long"]"#,
+                Value::Union(1, Box::new(Value::Null)),
+            ),
+            (
+                r#"{"type": "record", "name": "Test", "fields": [{"name": "f", 
"type": "long"}]}"#,
+                Value::Record(vec![("f".to_string(), Value::Long(1))]),
+            ),
+        ]
+    })
+}
 
-    static ref DEFAULT_VALUE_EXAMPLES: Vec<(&'static str, &'static str, 
Value)> = vec![
-        (r#""null""#, "null", Value::Null),
-        (r#""boolean""#, "true", Value::Boolean(true)),
-        (r#""string""#, r#""foo""#, Value::String("foo".to_string())),
-        (r#""bytes""#, r#""a""#, Value::Bytes(vec![97])), // ASCII 'a' => one 
byte
-        (r#""bytes""#, r#""\u00FF""#, Value::Bytes(vec![195, 191])), // The 
value is between U+0080 and U+07FF => two bytes
-        (r#""int""#, "5", Value::Int(5)),
-        (r#""long""#, "5", Value::Long(5)),
-        (r#""float""#, "1.1", Value::Float(1.1)),
-        (r#""double""#, "1.1", Value::Double(1.1)),
-        (r#"{"type": "fixed", "name": "F", "size": 2}"#, r#""a""#, 
Value::Fixed(1, vec![97])), // ASCII 'a' => one byte
-        (r#"{"type": "fixed", "name": "F", "size": 2}"#, r#""\u00FF""#, 
Value::Fixed(2, vec![195, 191])), // The value is between U+0080 and U+07FF => 
two bytes
-        (r#"{"type": "enum", "name": "F", "symbols": ["FOO", "BAR"]}"#, 
r#""FOO""#, Value::Enum(0, "FOO".to_string())),
-        (r#"{"type": "array", "items": "int"}"#, "[1, 2, 3]", 
Value::Array(vec![Value::Int(1), Value::Int(2), Value::Int(3)])),
-        (r#"{"type": "map", "values": "int"}"#, r#"{"a": 1, "b": 2}"#, 
Value::Map([("a".to_string(), Value::Int(1)), ("b".to_string(), 
Value::Int(2))].iter().cloned().collect())),
-        (r#"["int", "null"]"#, "5", Value::Union(0, Box::new(Value::Int(5)))),
-        (r#"{"type": "record", "name": "F", "fields": [{"name": "A", "type": 
"int"}]}"#, r#"{"A": 5}"#,Value::Record(vec![("A".to_string(), 
Value::Int(5))])),
-        (r#"["null", "int"]"#, "null", Value::Union(0, Box::new(Value::Null))),
-    ];
+fn binary_encodings() -> &'static Vec<(i64, Vec<u8>)> {
+    static BINARY_ENCODINGS_ONCE: OnceLock<Vec<(i64, Vec<u8>)>> = 
OnceLock::new();
+    BINARY_ENCODINGS_ONCE.get_or_init(|| {
+        vec![
+            (0, vec![0x00]),
+            (-1, vec![0x01]),
+            (1, vec![0x02]),
+            (-2, vec![0x03]),
+            (2, vec![0x04]),
+            (-64, vec![0x7f]),
+            (64, vec![0x80, 0x01]),
+            (8192, vec![0x80, 0x80, 0x01]),
+            (-8193, vec![0x81, 0x80, 0x01]),
+        ]
+    })
+}
 
-    static ref LONG_RECORD_SCHEMA: Schema = Schema::parse_str(r#"
-    {
-        "type": "record",
-        "name": "Test",
-        "fields": [
-            {"name": "A", "type": "int"},
-            {"name": "B", "type": "int"},
-            {"name": "C", "type": "int"},
-            {"name": "D", "type": "int"},
-            {"name": "E", "type": "int"},
-            {"name": "F", "type": "int"},
-            {"name": "G", "type": "int"}
+fn default_value_examples() -> &'static Vec<(&'static str, &'static str, 
Value)> {
+    static DEFAULT_VALUE_EXAMPLES_ONCE: OnceLock<Vec<(&'static str, &'static 
str, Value)>> =
+        OnceLock::new();
+    DEFAULT_VALUE_EXAMPLES_ONCE.get_or_init(|| {
+        vec![
+            (r#""null""#, "null", Value::Null),
+            (r#""boolean""#, "true", Value::Boolean(true)),
+            (r#""string""#, r#""foo""#, Value::String("foo".to_string())),
+            (r#""bytes""#, r#""a""#, Value::Bytes(vec![97])), // ASCII 'a' => 
one byte
+            (r#""bytes""#, r#""\u00FF""#, Value::Bytes(vec![195, 191])), // 
The value is between U+0080 and U+07FF => two bytes
+            (r#""int""#, "5", Value::Int(5)),
+            (r#""long""#, "5", Value::Long(5)),
+            (r#""float""#, "1.1", Value::Float(1.1)),
+            (r#""double""#, "1.1", Value::Double(1.1)),
+            (
+                r#"{"type": "fixed", "name": "F", "size": 2}"#,
+                r#""a""#,
+                Value::Fixed(1, vec![97]),
+            ), // ASCII 'a' => one byte
+            (
+                r#"{"type": "fixed", "name": "F", "size": 2}"#,
+                r#""\u00FF""#,
+                Value::Fixed(2, vec![195, 191]),
+            ), // The value is between U+0080 and U+07FF => two bytes
+            (
+                r#"{"type": "enum", "name": "F", "symbols": ["FOO", "BAR"]}"#,
+                r#""FOO""#,
+                Value::Enum(0, "FOO".to_string()),
+            ),
+            (
+                r#"{"type": "array", "items": "int"}"#,
+                "[1, 2, 3]",
+                Value::Array(vec![Value::Int(1), Value::Int(2), 
Value::Int(3)]),
+            ),
+            (
+                r#"{"type": "map", "values": "int"}"#,
+                r#"{"a": 1, "b": 2}"#,
+                Value::Map(
+                    [
+                        ("a".to_string(), Value::Int(1)),
+                        ("b".to_string(), Value::Int(2)),
+                    ]
+                    .iter()
+                    .cloned()
+                    .collect(),
+                ),
+            ),
+            (
+                r#"["int", "null"]"#,
+                "5",
+                Value::Union(0, Box::new(Value::Int(5))),
+            ),
+            (
+                r#"{"type": "record", "name": "F", "fields": [{"name": "A", 
"type": "int"}]}"#,
+                r#"{"A": 5}"#,
+                Value::Record(vec![("A".to_string(), Value::Int(5))]),
+            ),
+            (
+                r#"["null", "int"]"#,
+                "null",
+                Value::Union(0, Box::new(Value::Null)),
+            ),
         ]
-    }
-    "#).unwrap();
+    })
+}
 
-    static ref LONG_RECORD_DATUM: Value = Value::Record(vec![
-        ("A".to_string(), Value::Int(1)),
-        ("B".to_string(), Value::Int(2)),
-        ("C".to_string(), Value::Int(3)),
-        ("D".to_string(), Value::Int(4)),
-        ("E".to_string(), Value::Int(5)),
-        ("F".to_string(), Value::Int(6)),
-        ("G".to_string(), Value::Int(7)),
-    ]);
+fn long_record_schema() -> &'static Schema {
+    static LONG_RECORD_SCHEMA_ONCE: OnceLock<Schema> = OnceLock::new();
+    LONG_RECORD_SCHEMA_ONCE.get_or_init(|| {
+        Schema::parse_str(
+            r#"
+{
+    "type": "record",
+    "name": "Test",
+    "fields": [
+        {"name": "A", "type": "int"},
+        {"name": "B", "type": "int"},
+        {"name": "C", "type": "int"},
+        {"name": "D", "type": "int"},
+        {"name": "E", "type": "int"},
+        {"name": "F", "type": "int"},
+        {"name": "G", "type": "int"}
+    ]
+}
+"#,
+        )
+        .unwrap()
+    })
+}
+
+fn long_record_datum() -> &'static Value {
+    static LONG_RECORD_DATUM_ONCE: OnceLock<Value> = OnceLock::new();
+    LONG_RECORD_DATUM_ONCE.get_or_init(|| {
+        Value::Record(vec![
+            ("A".to_string(), Value::Int(1)),
+            ("B".to_string(), Value::Int(2)),
+            ("C".to_string(), Value::Int(3)),
+            ("D".to_string(), Value::Int(4)),
+            ("E".to_string(), Value::Int(5)),
+            ("F".to_string(), Value::Int(6)),
+            ("G".to_string(), Value::Int(7)),
+        ])
+    })
 }
 
 #[test]
 fn test_validate() -> TestResult {
-    for (raw_schema, value) in SCHEMAS_TO_VALIDATE.iter() {
+    for (raw_schema, value) in schemas_to_validate().iter() {
         let schema = Schema::parse_str(raw_schema)?;
         assert!(
             value.validate(&schema),
@@ -114,7 +213,7 @@ fn test_validate() -> TestResult {
 
 #[test]
 fn test_round_trip() -> TestResult {
-    for (raw_schema, value) in SCHEMAS_TO_VALIDATE.iter() {
+    for (raw_schema, value) in schemas_to_validate().iter() {
         let schema = Schema::parse_str(raw_schema)?;
         let encoded = to_avro_datum(&schema, value.clone()).unwrap();
         let decoded = from_avro_datum(&schema, &mut Cursor::new(encoded), 
None).unwrap();
@@ -126,7 +225,7 @@ fn test_round_trip() -> TestResult {
 
 #[test]
 fn test_binary_int_encoding() -> TestResult {
-    for (number, hex_encoding) in BINARY_ENCODINGS.iter() {
+    for (number, hex_encoding) in binary_encodings().iter() {
         let encoded = to_avro_datum(&Schema::Int, Value::Int(*number as i32))?;
         assert_eq!(&encoded, hex_encoding);
     }
@@ -136,7 +235,7 @@ fn test_binary_int_encoding() -> TestResult {
 
 #[test]
 fn test_binary_long_encoding() -> TestResult {
-    for (number, hex_encoding) in BINARY_ENCODINGS.iter() {
+    for (number, hex_encoding) in binary_encodings().iter() {
         let encoded = to_avro_datum(&Schema::Long, Value::Long(*number))?;
         assert_eq!(&encoded, hex_encoding);
     }
@@ -196,7 +295,7 @@ fn test_unknown_symbol() -> TestResult {
 
 #[test]
 fn test_default_value() -> TestResult {
-    for (field_type, default_json, default_datum) in 
DEFAULT_VALUE_EXAMPLES.iter() {
+    for (field_type, default_json, default_datum) in 
default_value_examples().iter() {
         let reader_schema = Schema::parse_str(&format!(
             r#"{{
                 "type": "record",
@@ -207,9 +306,9 @@ fn test_default_value() -> TestResult {
             }}"#
         ))?;
         let datum_to_read = Value::Record(vec![("H".to_string(), 
default_datum.clone())]);
-        let encoded = to_avro_datum(&LONG_RECORD_SCHEMA, 
LONG_RECORD_DATUM.clone())?;
+        let encoded = to_avro_datum(long_record_schema(), 
long_record_datum().clone())?;
         let datum_read = from_avro_datum(
-            &LONG_RECORD_SCHEMA,
+            long_record_schema(),
             &mut Cursor::new(encoded),
             Some(&reader_schema),
         )?;
@@ -234,9 +333,9 @@ fn test_no_default_value() -> TestResult {
             ]
         }"#,
     )?;
-    let encoded = to_avro_datum(&LONG_RECORD_SCHEMA, 
LONG_RECORD_DATUM.clone())?;
+    let encoded = to_avro_datum(long_record_schema(), 
long_record_datum().clone())?;
     let result = from_avro_datum(
-        &LONG_RECORD_SCHEMA,
+        long_record_schema(),
         &mut Cursor::new(encoded),
         Some(&reader_schema),
     );
@@ -263,9 +362,9 @@ fn test_projection() -> TestResult {
         ("E".to_string(), Value::Int(5)),
         ("F".to_string(), Value::Int(6)),
     ]);
-    let encoded = to_avro_datum(&LONG_RECORD_SCHEMA, 
LONG_RECORD_DATUM.clone())?;
+    let encoded = to_avro_datum(long_record_schema(), 
long_record_datum().clone())?;
     let datum_read = from_avro_datum(
-        &LONG_RECORD_SCHEMA,
+        long_record_schema(),
         &mut Cursor::new(encoded),
         Some(&reader_schema),
     )?;
@@ -292,9 +391,9 @@ fn test_field_order() -> TestResult {
         ("F".to_string(), Value::Int(6)),
         ("E".to_string(), Value::Int(5)),
     ]);
-    let encoded = to_avro_datum(&LONG_RECORD_SCHEMA, 
LONG_RECORD_DATUM.clone())?;
+    let encoded = to_avro_datum(long_record_schema(), 
long_record_datum().clone())?;
     let datum_read = from_avro_datum(
-        &LONG_RECORD_SCHEMA,
+        long_record_schema(),
         &mut Cursor::new(encoded),
         Some(&reader_schema),
     )?;
diff --git a/lang/rust/avro/tests/schema.rs b/lang/rust/avro/tests/schema.rs
index d548281da..7851d957d 100644
--- a/lang/rust/avro/tests/schema.rs
+++ b/lang/rust/avro/tests/schema.rs
@@ -18,6 +18,7 @@
 use std::{
     collections::HashMap,
     io::{Cursor, Read},
+    sync::OnceLock,
 };
 
 use apache_avro::{
@@ -28,7 +29,6 @@ use apache_avro::{
     Codec, Error, Reader, Schema, Writer,
 };
 use apache_avro_test_helper::{init, TestResult};
-use lazy_static::lazy_static;
 
 const PRIMITIVE_EXAMPLES: &[(&str, bool)] = &[
     (r#""null""#, true),
@@ -628,31 +628,37 @@ const LOCAL_TIMESTAMPMICROS_LOGICAL_TYPE: &[(&str, bool)] 
= &[
     ),
 ];
 
-lazy_static! {
-    static ref EXAMPLES: Vec<(&'static str, bool)> = Vec::new()
-        .iter()
-        .copied()
-        .chain(PRIMITIVE_EXAMPLES.iter().copied())
-        .chain(FIXED_EXAMPLES.iter().copied())
-        .chain(ENUM_EXAMPLES.iter().copied())
-        .chain(ARRAY_EXAMPLES.iter().copied())
-        .chain(MAP_EXAMPLES.iter().copied())
-        .chain(UNION_EXAMPLES.iter().copied())
-        .chain(RECORD_EXAMPLES.iter().copied())
-        .chain(DOC_EXAMPLES.iter().copied())
-        .chain(OTHER_ATTRIBUTES_EXAMPLES.iter().copied())
-        .chain(DECIMAL_LOGICAL_TYPE.iter().copied())
-        .chain(DECIMAL_LOGICAL_TYPE_ATTRIBUTES.iter().copied())
-        .chain(DATE_LOGICAL_TYPE.iter().copied())
-        .chain(TIMEMILLIS_LOGICAL_TYPE.iter().copied())
-        .chain(TIMEMICROS_LOGICAL_TYPE.iter().copied())
-        .chain(TIMESTAMPMILLIS_LOGICAL_TYPE.iter().copied())
-        .chain(TIMESTAMPMICROS_LOGICAL_TYPE.iter().copied())
-        .chain(LOCAL_TIMESTAMPMILLIS_LOGICAL_TYPE.iter().copied())
-        .chain(LOCAL_TIMESTAMPMICROS_LOGICAL_TYPE.iter().copied())
-        .collect();
-    static ref VALID_EXAMPLES: Vec<(&'static str, bool)> =
-        EXAMPLES.iter().copied().filter(|s| s.1).collect();
+fn examples() -> &'static Vec<(&'static str, bool)> {
+    static EXAMPLES_ONCE: OnceLock<Vec<(&'static str, bool)>> = 
OnceLock::new();
+    EXAMPLES_ONCE.get_or_init(|| {
+        Vec::new()
+            .iter()
+            .copied()
+            .chain(PRIMITIVE_EXAMPLES.iter().copied())
+            .chain(FIXED_EXAMPLES.iter().copied())
+            .chain(ENUM_EXAMPLES.iter().copied())
+            .chain(ARRAY_EXAMPLES.iter().copied())
+            .chain(MAP_EXAMPLES.iter().copied())
+            .chain(UNION_EXAMPLES.iter().copied())
+            .chain(RECORD_EXAMPLES.iter().copied())
+            .chain(DOC_EXAMPLES.iter().copied())
+            .chain(OTHER_ATTRIBUTES_EXAMPLES.iter().copied())
+            .chain(DECIMAL_LOGICAL_TYPE.iter().copied())
+            .chain(DECIMAL_LOGICAL_TYPE_ATTRIBUTES.iter().copied())
+            .chain(DATE_LOGICAL_TYPE.iter().copied())
+            .chain(TIMEMILLIS_LOGICAL_TYPE.iter().copied())
+            .chain(TIMEMICROS_LOGICAL_TYPE.iter().copied())
+            .chain(TIMESTAMPMILLIS_LOGICAL_TYPE.iter().copied())
+            .chain(TIMESTAMPMICROS_LOGICAL_TYPE.iter().copied())
+            .chain(LOCAL_TIMESTAMPMILLIS_LOGICAL_TYPE.iter().copied())
+            .chain(LOCAL_TIMESTAMPMICROS_LOGICAL_TYPE.iter().copied())
+            .collect()
+    })
+}
+
+fn valid_examples() -> &'static Vec<(&'static str, bool)> {
+    static VALID_EXAMPLES_ONCE: OnceLock<Vec<(&'static str, bool)>> = 
OnceLock::new();
+    VALID_EXAMPLES_ONCE.get_or_init(|| examples().iter().copied().filter(|s| 
s.1).collect())
 }
 
 #[test]
@@ -709,7 +715,7 @@ fn test_correct_recursive_extraction() -> TestResult {
 #[test]
 fn test_parse() -> TestResult {
     init();
-    for (raw_schema, valid) in EXAMPLES.iter() {
+    for (raw_schema, valid) in examples().iter() {
         let schema = Schema::parse_str(raw_schema);
         if *valid {
             assert!(
@@ -729,7 +735,7 @@ fn test_parse() -> TestResult {
 #[test]
 fn test_3799_parse_reader() -> TestResult {
     init();
-    for (raw_schema, valid) in EXAMPLES.iter() {
+    for (raw_schema, valid) in examples().iter() {
         let schema = Schema::parse_reader(&mut Cursor::new(raw_schema));
         if *valid {
             assert!(
@@ -745,7 +751,7 @@ fn test_3799_parse_reader() -> TestResult {
     }
 
     // Ensure it works for trait objects too.
-    for (raw_schema, valid) in EXAMPLES.iter() {
+    for (raw_schema, valid) in examples().iter() {
         let reader: &mut dyn Read = &mut Cursor::new(raw_schema);
         let schema = Schema::parse_reader(reader);
         if *valid {
@@ -785,7 +791,7 @@ fn test_3799_raise_io_error_from_parse_read() -> Result<(), 
String> {
 /// Test that the string generated by an Avro Schema object is, in fact, a 
valid Avro schema.
 fn test_valid_cast_to_string_after_parse() -> TestResult {
     init();
-    for (raw_schema, _) in VALID_EXAMPLES.iter() {
+    for (raw_schema, _) in valid_examples().iter() {
         let schema = Schema::parse_str(raw_schema)?;
         Schema::parse_str(schema.canonical_form().as_str())?;
     }
@@ -798,7 +804,7 @@ fn test_valid_cast_to_string_after_parse() -> TestResult {
 /// 3. Ensure "original" and "round trip" schemas are equivalent.
 fn test_equivalence_after_round_trip() -> TestResult {
     init();
-    for (raw_schema, _) in VALID_EXAMPLES.iter() {
+    for (raw_schema, _) in valid_examples().iter() {
         let original_schema = Schema::parse_str(raw_schema)?;
         let round_trip_schema = 
Schema::parse_str(original_schema.canonical_form().as_str())?;
         assert_eq!(original_schema, round_trip_schema);
diff --git a/lang/rust/avro_test_helper/Cargo.toml 
b/lang/rust/avro_test_helper/Cargo.toml
index 7f3198951..f88ce6d2c 100644
--- a/lang/rust/avro_test_helper/Cargo.toml
+++ b/lang/rust/avro_test_helper/Cargo.toml
@@ -35,6 +35,5 @@ anyhow = { default-features = false, version = "1.0.75", 
features = ["std"] }
 better-panic = { default-features = false, version = "0.3.0" }
 ctor = { default-features = false, version = "0.2.5" }
 env_logger = { default-features = false, version = "0.10.1" }
-lazy_static = { workspace = true }
 log = { workspace = true }
 ref_thread_local = { default-features = false, version = "0.1.1" }
diff --git a/lang/rust/avro_test_helper/src/logger.rs 
b/lang/rust/avro_test_helper/src/logger.rs
index 09fc1bede..505e42541 100644
--- a/lang/rust/avro_test_helper/src/logger.rs
+++ b/lang/rust/avro_test_helper/src/logger.rs
@@ -16,9 +16,9 @@
 // under the License.
 
 use crate::LOG_MESSAGES;
-use lazy_static::lazy_static;
 use log::{LevelFilter, Log, Metadata};
 use ref_thread_local::RefThreadLocal;
+use std::sync::OnceLock;
 
 struct TestLogger {
     delegate: env_logger::Logger,
@@ -41,14 +41,15 @@ impl Log for TestLogger {
     fn flush(&self) {}
 }
 
-lazy_static! {
+fn test_logger() -> &'static TestLogger {
     // Lazy static because the Logger has to be 'static
-    static ref TEST_LOGGER: TestLogger = TestLogger {
+    static TEST_LOGGER_ONCE: OnceLock<TestLogger> = OnceLock::new();
+    TEST_LOGGER_ONCE.get_or_init(|| TestLogger {
         delegate: env_logger::Builder::from_default_env()
             .filter_level(LevelFilter::Off)
             .parse_default_env()
             .build(),
-    };
+    })
 }
 
 pub fn clear_log_messages() {
@@ -70,7 +71,7 @@ pub fn assert_logged(expected_message: &str) {
 
 #[cfg(not(target_arch = "wasm32"))]
 pub(crate) fn install() {
-    log::set_logger(&*TEST_LOGGER)
+    log::set_logger(test_logger())
         .map(|_| log::set_max_level(LevelFilter::Trace))
         .map_err(|err| {
             eprintln!("Failed to set the custom logger: {err:?}");
diff --git a/share/docker/Dockerfile b/share/docker/Dockerfile
index d1b70403b..29978da04 100644
--- a/share/docker/Dockerfile
+++ b/share/docker/Dockerfile
@@ -193,7 +193,7 @@ RUN gem install bundler --no-document && \
     cd /tmp/lang/ruby && bundle install
 
 # Install Rust
-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 
--default-toolchain 1.65.0
+RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 
--default-toolchain 1.70.0
 
 # Note: This "ubertool" container has two JDK versions:
 # - OpenJDK 8

Reply via email to