You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by kr...@apache.org on 2026/03/01 09:51:05 UTC

(avro-rs) branch fix/ref_option created (now e07d7a8)

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

kriskras99 pushed a change to branch fix/ref_option
in repository https://gitbox.apache.org/repos/asf/avro-rs.git


      at e07d7a8  fix: Enable `ref_option` lint and fix the warnings it generates

This branch includes the following new commits:

     new fb7513f  feat: Rework `Name` to be more performant
     new e07d7a8  fix: Enable `ref_option` lint and fix the warnings it generates

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(avro-rs) 02/02: fix: Enable `ref_option` lint and fix the warnings it generates

Posted by kr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kriskras99 pushed a commit to branch fix/ref_option
in repository https://gitbox.apache.org/repos/asf/avro-rs.git

commit e07d7a88da07fe7ed8e3475942ddee930e439ce8
Author: Kriskras99 <gi...@kriskras99.nl>
AuthorDate: Sun Mar 1 10:50:53 2026 +0100

    fix: Enable `ref_option` lint and fix the warnings it generates
---
 .clippy.toml                      |  1 +
 Cargo.toml                        |  1 +
 avro/src/bytes.rs                 |  3 +++
 avro/src/schema/parser.rs         |  2 +-
 avro/src/schema/record/field.rs   |  8 ++++----
 avro/src/schema/union.rs          |  6 +++---
 avro/src/serde/derive.rs          | 40 +++++++++++++++++++++++++++++++--------
 avro/src/serde/with.rs            |  3 +++
 avro/src/types.rs                 | 31 +++++++++++++++++-------------
 avro_derive/src/attributes/mod.rs |  4 ++--
 10 files changed, 68 insertions(+), 31 deletions(-)

diff --git a/.clippy.toml b/.clippy.toml
new file mode 100644
index 0000000..87e1f23
--- /dev/null
+++ b/.clippy.toml
@@ -0,0 +1 @@
+avoid-breaking-exported-api = false
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 9ca5059..fcba48e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,6 +56,7 @@ opt-level = "s"
 clippy.all = { level = "warn", priority = -1 }
 clippy.too_long_first_doc_paragraph = "warn"
 clippy.doc_markdown = "warn"
+clippy.ref_option = "warn"
 # TODO: Needs more work
 #clippy.missing_errors_doc = "warn"
 #clippy.missing_panics_doc = "warn"
diff --git a/avro/src/bytes.rs b/avro/src/bytes.rs
index 341b01d..869bbfc 100644
--- a/avro/src/bytes.rs
+++ b/avro/src/bytes.rs
@@ -69,6 +69,7 @@ pub mod serde_avro_bytes_opt {
         since = "0.22.0",
         note = "Use `apache_avro::serde::bytes_opt::serialize` instead"
     )]
+    #[expect(clippy::ref_option, reason = "Required by the Serde API")]
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
@@ -135,6 +136,7 @@ pub mod serde_avro_fixed_opt {
         since = "0.22.0",
         note = "Use `apache_avro::serde::fixed_opt::serialize` instead"
     )]
+    #[expect(clippy::ref_option, reason = "Required by the Serde API")]
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
@@ -201,6 +203,7 @@ pub mod serde_avro_slice_opt {
         since = "0.22.0",
         note = "Use `apache_avro::serde::slice_opt::serialize` instead"
     )]
+    #[expect(clippy::ref_option, reason = "Required by the Serde API")]
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
diff --git a/avro/src/schema/parser.rs b/avro/src/schema/parser.rs
index 0f89018..f0a234d 100644
--- a/avro/src/schema/parser.rs
+++ b/avro/src/schema/parser.rs
@@ -658,7 +658,7 @@ impl Parser {
 
         if let Some(ref value) = default {
             let resolved = types::Value::from(value.clone())
-                .resolve_enum(&symbols, &Some(value.to_string()), &None)
+                .resolve_enum(&symbols, Some(value.as_str()), None)
                 .is_ok();
             if !resolved {
                 return Err(Details::GetEnumDefault {
diff --git a/avro/src/schema/record/field.rs b/avro/src/schema/record/field.rs
index 11326e1..f8dce53 100644
--- a/avro/src/schema/record/field.rs
+++ b/avro/src/schema/record/field.rs
@@ -106,7 +106,7 @@ impl RecordField {
             name,
             &enclosing_record.fullname(None),
             parser.get_parsed_schemas(),
-            &default,
+            default.as_ref(),
         )?;
 
         let aliases = field
@@ -137,7 +137,7 @@ impl RecordField {
         field_name: &str,
         record_name: &str,
         names: &Names,
-        default: &Option<Value>,
+        default: Option<&Value>,
     ) -> AvroResult<()> {
         if let Some(value) = default {
             let avro_value = types::Value::try_from(value.clone())?;
@@ -147,7 +147,7 @@ impl RecordField {
                     let resolved = schemas.iter().any(|schema| {
                         avro_value
                             .to_owned()
-                            .resolve_internal(schema, names, schema.namespace(), &None)
+                            .resolve_internal(schema, names, schema.namespace(), None)
                             .is_ok()
                     });
 
@@ -165,7 +165,7 @@ impl RecordField {
                 }
                 _ => {
                     let resolved = avro_value
-                        .resolve_internal(field_schema, names, field_schema.namespace(), &None)
+                        .resolve_internal(field_schema, names, field_schema.namespace(), None)
                         .is_ok();
 
                     if !resolved {
diff --git a/avro/src/schema/union.rs b/avro/src/schema/union.rs
index 31affe7..24edfee 100644
--- a/avro/src/schema/union.rs
+++ b/avro/src/schema/union.rs
@@ -106,7 +106,7 @@ impl UnionSchema {
                     // TODO: Do this without the clone
                     value
                         .clone()
-                        .resolve_internal(schema, known_schemata, namespace, &None)
+                        .resolve_internal(schema, known_schemata, namespace, None)
                         .ok()
                         .map(|_| (index, schema))
                 } else {
@@ -130,7 +130,7 @@ impl UnionSchema {
                     // TODO: Do this without the clone
                     value
                         .clone()
-                        .resolve_internal(schema, known_schemata, namespace, &None)
+                        .resolve_internal(schema, known_schemata, namespace, None)
                         .is_ok()
                 })
         });
@@ -148,7 +148,7 @@ impl UnionSchema {
                     // TODO: Do this without the clone
                     value
                         .clone()
-                        .resolve_internal(schema, known_schemata, namespace, &None)
+                        .resolve_internal(schema, known_schemata, namespace, None)
                         .is_ok()
                 })
             }
diff --git a/avro/src/serde/derive.rs b/avro/src/serde/derive.rs
index d979962..770d11b 100644
--- a/avro/src/serde/derive.rs
+++ b/avro/src/serde/derive.rs
@@ -572,7 +572,10 @@ where
         Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build()
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -588,7 +591,10 @@ where
         Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build()
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -611,7 +617,10 @@ where
         )
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
         None
     }
 
@@ -645,7 +654,10 @@ impl AvroSchemaComponent for core::time::Duration {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -675,7 +687,10 @@ impl AvroSchemaComponent for uuid::Uuid {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -703,7 +718,10 @@ impl AvroSchemaComponent for u64 {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -731,7 +749,10 @@ impl AvroSchemaComponent for u128 {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -759,7 +780,10 @@ impl AvroSchemaComponent for i128 {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(
+        _: &mut HashSet<Name>,
+        _: NamespaceRef,
+    ) -> Option<Vec<RecordField>> {
         None
     }
 }
diff --git a/avro/src/serde/with.rs b/avro/src/serde/with.rs
index 29744e1..d6a2a8b 100644
--- a/avro/src/serde/with.rs
+++ b/avro/src/serde/with.rs
@@ -186,6 +186,7 @@ pub mod bytes_opt {
         None
     }
 
+    #[expect(clippy::ref_option, reason = "Required by the Serde API")]
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
@@ -343,6 +344,7 @@ pub mod fixed_opt {
         None
     }
 
+    #[expect(clippy::ref_option, reason = "Required by the Serde API")]
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
@@ -484,6 +486,7 @@ pub mod slice_opt {
         None
     }
 
+    #[expect(clippy::ref_option, reason = "Required by the Serde API")]
     pub fn serialize<S, B>(bytes: &Option<B>, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
diff --git a/avro/src/types.rs b/avro/src/types.rs
index 90ea2b0..f7f0e55 100644
--- a/avro/src/types.rs
+++ b/avro/src/types.rs
@@ -674,7 +674,7 @@ impl Value {
         } else {
             ResolvedSchema::try_from(schemata)?
         };
-        self.resolve_internal(schema, rs.get_names(), enclosing_namespace, &None)
+        self.resolve_internal(schema, rs.get_names(), enclosing_namespace, None)
     }
 
     pub(crate) fn resolve_internal<S: Borrow<Schema> + Debug>(
@@ -682,7 +682,7 @@ impl Value {
         schema: &Schema,
         names: &HashMap<Name, S>,
         enclosing_namespace: NamespaceRef,
-        field_default: &Option<JsonValue>,
+        field_default: Option<&JsonValue>,
     ) -> AvroResult<Self> {
         // Check if this schema is a union, and if the reader schema is not.
         if SchemaKind::from(&self) == SchemaKind::Union
@@ -721,7 +721,7 @@ impl Value {
             }
             Schema::Enum(EnumSchema {
                 symbols, default, ..
-            }) => self.resolve_enum(symbols, default, field_default),
+            }) => self.resolve_enum(symbols, default.as_deref(), field_default),
             Schema::Array(inner) => self.resolve_array(&inner.items, names, enclosing_namespace),
             Schema::Map(inner) => self.resolve_map(&inner.types, names, enclosing_namespace),
             Schema::Record(RecordSchema { fields, .. }) => {
@@ -1039,8 +1039,8 @@ impl Value {
     pub(crate) fn resolve_enum(
         self,
         symbols: &[String],
-        enum_default: &Option<String>,
-        _field_default: &Option<JsonValue>,
+        enum_default: Option<&str>,
+        _field_default: Option<&JsonValue>,
     ) -> Result<Self, Error> {
         let validate_symbol = |symbol: String, symbols: &[String]| {
             if let Some(index) = symbols.iter().position(|item| item == &symbol) {
@@ -1049,7 +1049,7 @@ impl Value {
                 match enum_default {
                     Some(default) => {
                         if let Some(index) = symbols.iter().position(|item| item == default) {
-                            Ok(Value::Enum(index as u32, default.clone()))
+                            Ok(Value::Enum(index as u32, default.to_string()))
                         } else {
                             Err(Details::GetEnumDefault {
                                 symbol,
@@ -1079,7 +1079,7 @@ impl Value {
         schema: &UnionSchema,
         names: &HashMap<Name, S>,
         enclosing_namespace: NamespaceRef,
-        field_default: &Option<JsonValue>,
+        field_default: Option<&JsonValue>,
     ) -> Result<Self, Error> {
         let v = match self {
             // Both are unions case.
@@ -1110,7 +1110,7 @@ impl Value {
             Value::Array(items) => Ok(Value::Array(
                 items
                     .into_iter()
-                    .map(|item| item.resolve_internal(schema, names, enclosing_namespace, &None))
+                    .map(|item| item.resolve_internal(schema, names, enclosing_namespace, None))
                     .collect::<Result<_, _>>()?,
             )),
             other => Err(Details::GetArray {
@@ -1133,7 +1133,7 @@ impl Value {
                     .into_iter()
                     .map(|(key, value)| {
                         value
-                            .resolve_internal(schema, names, enclosing_namespace, &None)
+                            .resolve_internal(schema, names, enclosing_namespace, None)
                             .map(|value| (key, value))
                     })
                     .collect::<Result<_, _>>()?,
@@ -1177,8 +1177,8 @@ impl Value {
                                 ..
                             }) => Value::try_from(value.clone())?.resolve_enum(
                                 symbols,
-                                default,
-                                &field.default.clone(),
+                                default.as_deref(),
+                                field.default.as_ref(),
                             )?,
                             Schema::Union(ref union_schema) => {
                                 let first = &union_schema.variants()[0];
@@ -1193,7 +1193,7 @@ impl Value {
                                                 first,
                                                 names,
                                                 enclosing_namespace,
-                                                &field.default,
+                                                field.default.as_ref(),
                                             )?,
                                         ),
                                     ),
@@ -1207,7 +1207,12 @@ impl Value {
                     },
                 };
                 value
-                    .resolve_internal(&field.schema, names, enclosing_namespace, &field.default)
+                    .resolve_internal(
+                        &field.schema,
+                        names,
+                        enclosing_namespace,
+                        field.default.as_ref(),
+                    )
                     .map(|value| (field.name.clone(), value))
             })
             .collect::<Result<Vec<_>, _>>()?;
diff --git a/avro_derive/src/attributes/mod.rs b/avro_derive/src/attributes/mod.rs
index 27c1cbc..cfdae4f 100644
--- a/avro_derive/src/attributes/mod.rs
+++ b/avro_derive/src/attributes/mod.rs
@@ -202,7 +202,7 @@ pub enum With {
 impl With {
     fn from_avro_and_serde(
         avro: &avro::With,
-        serde: &Option<String>,
+        serde: Option<&str>,
         span: Span,
     ) -> Result<Self, syn::Error> {
         match &avro {
@@ -327,7 +327,7 @@ impl FieldOptions {
                 "`#[serde(skip_serializing)]` and `#[serde(skip_serializing_if)]` are incompatible with `#[avro(default = false)]`"
             ));
         }
-        let with = match With::from_avro_and_serde(&avro.with, &serde.with, span) {
+        let with = match With::from_avro_and_serde(&avro.with, serde.with.as_deref(), span) {
             Ok(with) => with,
             Err(error) => {
                 errors.push(error);


(avro-rs) 01/02: feat: Rework `Name` to be more performant

Posted by kr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kriskras99 pushed a commit to branch fix/ref_option
in repository https://gitbox.apache.org/repos/asf/avro-rs.git

commit fb7513f2d33c4cda5ef2700db5d6174bf6d5eaa9
Author: Kriskras99 <gi...@kriskras99.nl>
AuthorDate: Sat Feb 28 21:10:50 2026 +0100

    feat: Rework `Name` to be more performant
    
    This is achieved by:
    - Making `Name` one allocation instead of two
    - Returning references as much as possible to reduce cloning
      - `.namespace()` returns a `NamespaceRef<'_>` which is a `Option<&'_ str>`
      - `fully_qualified_name` returns a `Cow<'_, Name>` which reduces allocations
        when the name already has a namespace or there is enclosing namespace.
    - Allow efficient construction of the name when there is an enclosing namespace
    - `Display` and `fullname(None)` can now return the full name directly
    
    This required changing the `SchemaNameValidator` trait which now needs to return
    the index of the starting byte of the name. Included in this change is providing
    default implementations for `*Validator::validate` functions. This allows users to
    easily implement the trait by just returning a different regex.
    
    # Conflicts:
    #       avro/src/schema/mod.rs
    #       avro/src/serde/derive.rs
    #       avro/src/types.rs
    #       avro/src/writer.rs
    #       avro/tests/get_record_fields.rs
    #       avro_derive/src/lib.rs
    #       avro_derive/tests/derive.rs
---
 avro/src/decode.rs                      |  17 ++-
 avro/src/encode.rs                      |  18 +--
 avro/src/reader/block.rs                |   6 +-
 avro/src/reader/mod.rs                  |   2 +-
 avro/src/reader/single_object.rs        |   2 +-
 avro/src/schema/mod.rs                  |  93 ++++++----------
 avro/src/schema/name.rs                 | 191 ++++++++++++++++++++------------
 avro/src/schema/parser.rs               |  71 ++++++------
 avro/src/schema/record/field.rs         |  17 ++-
 avro/src/schema/resolve.rs              |  63 ++++++-----
 avro/src/schema/union.rs                |  28 ++---
 avro/src/schema_compatibility.rs        |   6 +-
 avro/src/schema_equality.rs             |   5 +-
 avro/src/serde/derive.rs                | 109 +++++++++---------
 avro/src/serde/ser_schema/mod.rs        |  28 ++---
 avro/src/serde/with.rs                  |  50 +++++----
 avro/src/types.rs                       |  60 +++++-----
 avro/src/util.rs                        |  12 +-
 avro/src/validator.rs                   |  78 ++++++-------
 avro/src/writer.rs                      |  14 +--
 avro/tests/avro_schema_component.rs     |   4 +-
 avro/tests/get_record_fields.rs         |  12 +-
 avro/tests/schema.rs                    |  38 +++----
 avro/tests/serde_human_readable_true.rs |   2 +-
 avro/tests/validators.rs                |   5 +-
 avro_derive/src/lib.rs                  |  29 +++--
 avro_derive/tests/derive.rs             |  50 +++++----
 27 files changed, 508 insertions(+), 502 deletions(-)

diff --git a/avro/src/decode.rs b/avro/src/decode.rs
index dfa4bd3..cab6797 100644
--- a/avro/src/decode.rs
+++ b/avro/src/decode.rs
@@ -15,17 +15,14 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::schema::{InnerDecimalSchema, UuidSchema};
+use crate::schema::{InnerDecimalSchema, NamespaceRef, UuidSchema};
 use crate::{
     AvroResult, Error,
     bigdecimal::deserialize_big_decimal,
     decimal::Decimal,
     duration::Duration,
     error::Details,
-    schema::{
-        DecimalSchema, EnumSchema, FixedSchema, Name, Namespace, RecordSchema, ResolvedSchema,
-        Schema,
-    },
+    schema::{DecimalSchema, EnumSchema, FixedSchema, Name, RecordSchema, ResolvedSchema, Schema},
     types::Value,
     util::{safe_len, zag_i32, zag_i64},
 };
@@ -74,13 +71,13 @@ fn decode_seq_len<R: Read>(reader: &mut R) -> AvroResult<usize> {
 /// Decode a `Value` from avro format given its `Schema`.
 pub fn decode<R: Read>(schema: &Schema, reader: &mut R) -> AvroResult<Value> {
     let rs = ResolvedSchema::try_from(schema)?;
-    decode_internal(schema, rs.get_names(), &None, reader)
+    decode_internal(schema, rs.get_names(), None, reader)
 }
 
 pub(crate) fn decode_internal<R: Read, S: Borrow<Schema>>(
     schema: &Schema,
     names: &HashMap<Name, S>,
-    enclosing_namespace: &Namespace,
+    enclosing_namespace: NamespaceRef,
     reader: &mut R,
 ) -> AvroResult<Value> {
     match schema {
@@ -313,7 +310,7 @@ pub(crate) fn decode_internal<R: Read, S: Borrow<Schema>>(
                     decode_internal(
                         &field.schema,
                         names,
-                        &fully_qualified_name.namespace,
+                        fully_qualified_name.namespace(),
                         reader,
                     )?,
                 ));
@@ -344,11 +341,11 @@ pub(crate) fn decode_internal<R: Read, S: Borrow<Schema>>(
                 decode_internal(
                     resolved.borrow(),
                     names,
-                    &fully_qualified_name.namespace,
+                    fully_qualified_name.namespace(),
                     reader,
                 )
             } else {
-                Err(Details::SchemaResolutionError(fully_qualified_name).into())
+                Err(Details::SchemaResolutionError(fully_qualified_name.into_owned()).into())
             }
         }
     }
diff --git a/avro/src/encode.rs b/avro/src/encode.rs
index 656509b..2c7eb9f 100644
--- a/avro/src/encode.rs
+++ b/avro/src/encode.rs
@@ -15,14 +15,14 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use crate::schema::{InnerDecimalSchema, UuidSchema};
+use crate::schema::{InnerDecimalSchema, NamespaceRef, UuidSchema};
 use crate::{
     AvroResult,
     bigdecimal::serialize_big_decimal,
     error::Details,
     schema::{
-        DecimalSchema, EnumSchema, FixedSchema, Name, Namespace, RecordSchema, ResolvedSchema,
-        Schema, SchemaKind, UnionSchema,
+        DecimalSchema, EnumSchema, FixedSchema, Name, RecordSchema, ResolvedSchema, Schema,
+        SchemaKind, UnionSchema,
     },
     types::{Value, ValueKind},
     util::{zig_i32, zig_i64},
@@ -37,7 +37,7 @@ use std::{borrow::Borrow, collections::HashMap, io::Write};
 /// encoding for complex type values.
 pub fn encode<W: Write>(value: &Value, schema: &Schema, writer: &mut W) -> AvroResult<usize> {
     let rs = ResolvedSchema::try_from(schema)?;
-    encode_internal(value, schema, rs.get_names(), &None, writer)
+    encode_internal(value, schema, rs.get_names(), None, writer)
 }
 
 /// Encode `s` as the _bytes_ primitive type.
@@ -66,14 +66,16 @@ pub(crate) fn encode_internal<W: Write, S: Borrow<Schema>>(
     value: &Value,
     schema: &Schema,
     names: &HashMap<Name, S>,
-    enclosing_namespace: &Namespace,
+    enclosing_namespace: NamespaceRef,
     writer: &mut W,
 ) -> AvroResult<usize> {
     if let Schema::Ref { name } = schema {
         let fully_qualified_name = name.fully_qualified_name(enclosing_namespace);
         let resolved = names
             .get(&fully_qualified_name)
-            .ok_or(Details::SchemaResolutionError(fully_qualified_name))?;
+            .ok_or(Details::SchemaResolutionError(
+                fully_qualified_name.into_owned(),
+            ))?;
         return encode_internal(value, resolved.borrow(), names, enclosing_namespace, writer);
     }
 
@@ -290,7 +292,7 @@ pub(crate) fn encode_internal<W: Write, S: Borrow<Schema>>(
                 ..
             }) = *schema
             {
-                let record_namespace = name.fully_qualified_name(enclosing_namespace).namespace;
+                let record_namespace = name.namespace().or(enclosing_namespace);
 
                 let mut lookup = HashMap::new();
                 value_fields.iter().for_each(|(name, field)| {
@@ -312,7 +314,7 @@ pub(crate) fn encode_internal<W: Write, S: Borrow<Schema>>(
                             value,
                             &schema_field.schema,
                             names,
-                            &record_namespace,
+                            record_namespace,
                             writer,
                         )?;
                     } else {
diff --git a/avro/src/reader/block.rs b/avro/src/reader/block.rs
index 1571c06..06c07a2 100644
--- a/avro/src/reader/block.rs
+++ b/avro/src/reader/block.rs
@@ -188,7 +188,7 @@ impl<'r, R: Read> Block<'r, R> {
         let item = decode_internal(
             &self.writer_schema,
             &self.names_refs,
-            &None,
+            None,
             &mut block_bytes,
         )?;
         let item = match read_schema {
@@ -221,7 +221,7 @@ impl<'r, R: Read> Block<'r, R> {
             resolve_names_with_schemata(
                 self.schemata.iter().copied(),
                 &mut names,
-                &None,
+                None,
                 &HashMap::new(),
             )?;
             self.names_refs = names.into_iter().map(|(n, s)| (n, s.clone())).collect();
@@ -229,7 +229,7 @@ impl<'r, R: Read> Block<'r, R> {
         } else {
             self.writer_schema = Schema::parse(&json)?;
             let mut names = HashMap::new();
-            resolve_names(&self.writer_schema, &mut names, &None, &HashMap::new())?;
+            resolve_names(&self.writer_schema, &mut names, None, &HashMap::new())?;
             self.names_refs = names.into_iter().map(|(n, s)| (n, s.clone())).collect();
         }
         Ok(())
diff --git a/avro/src/reader/mod.rs b/avro/src/reader/mod.rs
index 598e1ff..0113e50 100644
--- a/avro/src/reader/mod.rs
+++ b/avro/src/reader/mod.rs
@@ -192,7 +192,7 @@ pub fn from_avro_datum_reader_schemata<R: Read>(
     reader_schemata: Vec<&Schema>,
 ) -> AvroResult<Value> {
     let rs = ResolvedSchema::try_from(writer_schemata)?;
-    let value = decode_internal(writer_schema, rs.get_names(), &None, reader)?;
+    let value = decode_internal(writer_schema, rs.get_names(), None, reader)?;
     match reader_schema {
         Some(schema) => {
             if reader_schemata.is_empty() {
diff --git a/avro/src/reader/single_object.rs b/avro/src/reader/single_object.rs
index 6fd61d1..c6151a3 100644
--- a/avro/src/reader/single_object.rs
+++ b/avro/src/reader/single_object.rs
@@ -55,7 +55,7 @@ impl GenericSingleObjectReader {
                     decode_internal(
                         self.write_schema.get_root_schema(),
                         self.write_schema.get_names(),
-                        &None,
+                        None,
                         reader,
                     )
                 } else {
diff --git a/avro/src/schema/mod.rs b/avro/src/schema/mod.rs
index a0f3077..40fc0f9 100644
--- a/avro/src/schema/mod.rs
+++ b/avro/src/schema/mod.rs
@@ -28,7 +28,7 @@ pub(crate) use crate::schema::resolve::{
     ResolvedOwnedSchema, resolve_names, resolve_names_with_schemata,
 };
 pub use crate::schema::{
-    name::{Alias, Aliases, Name, Names, NamesRef, Namespace},
+    name::{Alias, Aliases, Name, Names, NamesRef, Namespace, NamespaceRef},
     record::{RecordField, RecordFieldBuilder, RecordSchema, RecordSchemaBuilder},
     resolve::ResolvedSchema,
     union::{UnionSchema, UnionSchemaBuilder},
@@ -381,10 +381,10 @@ impl FixedSchema {
         S: Serializer,
     {
         map.serialize_entry("type", "fixed")?;
-        if let Some(n) = self.name.namespace.as_ref() {
+        if let Some(n) = self.name.namespace() {
             map.serialize_entry("namespace", n)?;
         }
-        map.serialize_entry("name", &self.name.name)?;
+        map.serialize_entry("name", &self.name.name())?;
         if let Some(docstr) = self.doc.as_ref() {
             map.serialize_entry("doc", docstr)?;
         }
@@ -406,10 +406,7 @@ impl FixedSchema {
     /// All other fields are `None` or empty.
     pub(crate) fn copy_only_size(&self) -> Self {
         Self {
-            name: Name {
-                name: String::new(),
-                namespace: None,
-            },
+            name: Name::invalid_empty_name(),
             aliases: None,
             doc: None,
             size: self.size,
@@ -556,7 +553,7 @@ impl Schema {
             let json = json.as_ref();
             let schema: JsonValue = serde_json::from_str(json).map_err(Details::ParseSchemaJson)?;
             if let JsonValue::Object(inner) = &schema {
-                let name = Name::parse(inner, &None)?;
+                let name = Name::parse(inner, None)?;
                 let previous_value = input_schemas.insert(name.clone(), schema);
                 if previous_value.is_some() {
                     return Err(Details::NameCollision(name.fullname(None)).into());
@@ -598,7 +595,7 @@ impl Schema {
             let json = json.as_ref();
             let schema: JsonValue = serde_json::from_str(json).map_err(Details::ParseSchemaJson)?;
             if let JsonValue::Object(inner) = &schema {
-                let name = Name::parse(inner, &None)?;
+                let name = Name::parse(inner, None)?;
                 if let Some(_previous) = input_schemas.insert(name.clone(), schema) {
                     return Err(Details::NameCollision(name.fullname(None)).into());
                 }
@@ -615,7 +612,7 @@ impl Schema {
         parser.parse_input_schemas()?;
 
         let value = serde_json::from_str(schema).map_err(Details::ParseSchemaJson)?;
-        let schema = parser.parse(&value, &None)?;
+        let schema = parser.parse(&value, None)?;
         let schemata = parser.parse_list()?;
         Ok((schema, schemata))
     }
@@ -632,14 +629,14 @@ impl Schema {
     /// Parses an Avro schema from JSON.
     pub fn parse(value: &JsonValue) -> AvroResult<Schema> {
         let mut parser = Parser::default();
-        parser.parse(value, &None)
+        parser.parse(value, None)
     }
 
     /// Parses an Avro schema from JSON.
     /// Any `Schema::Ref`s must be known in the `names` map.
     pub(crate) fn parse_with_names(value: &JsonValue, names: Names) -> AvroResult<Schema> {
         let mut parser = Parser::new(HashMap::with_capacity(1), Vec::with_capacity(1), names);
-        parser.parse(value, &None)
+        parser.parse(value, None)
     }
 
     /// Returns the custom attributes (metadata) if the schema supports them.
@@ -695,8 +692,8 @@ impl Schema {
     }
 
     /// Returns the namespace of the schema if it has one.
-    pub fn namespace(&self) -> Namespace {
-        self.name().and_then(|n| n.namespace.clone())
+    pub fn namespace(&self) -> NamespaceRef<'_> {
+        self.name().and_then(|n| n.namespace())
     }
 
     /// Returns the aliases of the schema if it has ones.
@@ -871,10 +868,10 @@ impl Serialize for Schema {
             }) => {
                 let mut map = serializer.serialize_map(None)?;
                 map.serialize_entry("type", "record")?;
-                if let Some(ref n) = name.namespace {
+                if let Some(ref n) = name.namespace() {
                     map.serialize_entry("namespace", n)?;
                 }
-                map.serialize_entry("name", &name.name)?;
+                map.serialize_entry("name", &name.name())?;
                 if let Some(docstr) = doc {
                     map.serialize_entry("doc", docstr)?;
                 }
@@ -897,10 +894,10 @@ impl Serialize for Schema {
             }) => {
                 let mut map = serializer.serialize_map(None)?;
                 map.serialize_entry("type", "enum")?;
-                if let Some(ref n) = name.namespace {
+                if let Some(ref n) = name.namespace() {
                     map.serialize_entry("namespace", n)?;
                 }
-                map.serialize_entry("name", &name.name)?;
+                map.serialize_entry("name", &name.name())?;
                 map.serialize_entry("symbols", symbols)?;
 
                 if let Some(aliases) = aliases {
@@ -1763,10 +1760,7 @@ mod tests {
         lookup.insert("next".to_owned(), 1);
 
         let expected = Schema::Record(RecordSchema {
-            name: Name {
-                name: "LongList".to_owned(),
-                namespace: None,
-            },
+            name: Name::new("LongList")?,
             aliases: Some(vec![Alias::new("LinkedLongs").unwrap()]),
             doc: None,
             fields: vec![
@@ -1776,15 +1770,12 @@ mod tests {
                     .build(),
                 RecordField::builder()
                     .name("next".to_string())
-                    .schema(Schema::Union(UnionSchema::new(vec![
+                    .schema(Schema::union(vec![
                         Schema::Null,
                         Schema::Ref {
-                            name: Name {
-                                name: "LongList".to_owned(),
-                                namespace: None,
-                            },
+                            name: Name::new("LongList")?,
                         },
-                    ])?))
+                    ])?)
                     .build(),
             ],
             lookup,
@@ -1819,10 +1810,7 @@ mod tests {
         lookup.insert("next".to_owned(), 1);
 
         let expected = Schema::Record(RecordSchema {
-            name: Name {
-                name: "record".to_owned(),
-                namespace: None,
-            },
+            name: Name::new("record")?,
             aliases: None,
             doc: None,
             fields: vec![
@@ -1833,10 +1821,7 @@ mod tests {
                 RecordField::builder()
                     .name("next".to_string())
                     .schema(Schema::Ref {
-                        name: Name {
-                            name: "record".to_owned(),
-                            namespace: None,
-                        },
+                        name: Name::new("record")?,
                     })
                     .build(),
             ],
@@ -1879,10 +1864,7 @@ mod tests {
         lookup.insert("next".to_owned(), 1);
 
         let expected = Schema::Record(RecordSchema {
-            name: Name {
-                name: "record".to_owned(),
-                namespace: None,
-            },
+            name: Name::new("record")?,
             aliases: None,
             doc: None,
             fields: vec![
@@ -1945,20 +1927,14 @@ mod tests {
         lookup.insert("next".to_owned(), 1);
 
         let expected = Schema::Record(RecordSchema {
-            name: Name {
-                name: "record".to_owned(),
-                namespace: None,
-            },
+            name: Name::new("record")?,
             aliases: None,
             doc: None,
             fields: vec![
                 RecordField::builder()
                     .name("fixed".to_string())
                     .schema(Schema::Fixed(FixedSchema {
-                        name: Name {
-                            name: "fixed".to_owned(),
-                            namespace: None,
-                        },
+                        name: Name::new("fixed")?,
                         aliases: None,
                         doc: None,
                         size: 456,
@@ -2246,8 +2222,8 @@ mod tests {
 
         let schema = Schema::parse_str(schema)?;
         if let Schema::Record(RecordSchema { name, .. }) = schema {
-            assert_eq!(name.name, "name");
-            assert_eq!(name.namespace, Some("space".to_string()));
+            assert_eq!(name.name(), "name");
+            assert_eq!(name.namespace(), Some("space"));
         } else {
             panic!("Expected a record schema!");
         }
@@ -2273,7 +2249,7 @@ mod tests {
 
         let schema = Schema::parse_str(schema)?;
         if let Schema::Record(RecordSchema { name, .. }) = schema {
-            assert_eq!(name.namespace, Some("space1".to_string()));
+            assert_eq!(name.namespace(), Some("space1"));
         } else {
             panic!("Expected a record schema!");
         }
@@ -2299,7 +2275,7 @@ mod tests {
 
         let schema = Schema::parse_str(schema)?;
         if let Schema::Record(RecordSchema { name, .. }) = schema {
-            assert_eq!(name.namespace, Some("space2".to_string()));
+            assert_eq!(name.namespace(), Some("space2"));
         } else {
             panic!("Expected a record schema!");
         }
@@ -3132,9 +3108,9 @@ mod tests {
         assert_eq!(canonical_form, expected);
 
         let name = Name::new("my_name")?;
-        let fullname = name.fullname(Some("".to_string()));
+        let fullname = name.fullname(Some(""));
         assert_eq!(fullname, "my_name");
-        let qname = name.fully_qualified_name(&Some("".to_string())).to_string();
+        let qname = name.fully_qualified_name(Some("")).to_string();
         assert_eq!(qname, "my_name");
 
         Ok(())
@@ -3483,7 +3459,7 @@ mod tests {
         let name = Name::new(".my_name")?;
         let fullname = name.fullname(None);
         assert_eq!(fullname, "my_name");
-        let qname = name.fully_qualified_name(&None).to_string();
+        let qname = name.fully_qualified_name(None).to_string();
         assert_eq!(qname, "my_name");
 
         Ok(())
@@ -4385,7 +4361,7 @@ mod tests {
             })
         );
         assert_logged(
-            r#"Ignoring uuid logical type for a Fixed schema because its size (6) is not 16! Schema: Fixed(FixedSchema { name: Name { name: "FixedUUID", namespace: None }, size: 6, .. })"#,
+            r#"Ignoring uuid logical type for a Fixed schema because its size (6) is not 16! Schema: Fixed(FixedSchema { name: Name { name: "FixedUUID", .. }, size: 6, .. })"#,
         );
 
         Ok(())
@@ -4487,10 +4463,7 @@ mod tests {
             let mut lookup = BTreeMap::new();
             lookup.insert("value".to_owned(), 0);
             Schema::Record(RecordSchema {
-                name: Name {
-                    name: "LongList".to_owned(),
-                    namespace: None,
-                },
+                name: Name::new("LongList")?,
                 aliases: Some(vec![Alias::new("LinkedLongs").unwrap()]),
                 doc: None,
                 fields: vec![
diff --git a/avro/src/schema/name.rs b/avro/src/schema/name.rs
index b551584..4f444ca 100644
--- a/avro/src/schema/name.rs
+++ b/avro/src/schema/name.rs
@@ -17,8 +17,9 @@
 
 use serde::{Deserialize, Serialize, Serializer};
 use serde_json::{Map, Value};
+use std::borrow::Cow;
 use std::collections::HashMap;
-use std::fmt;
+use std::fmt::{Debug, Display, Formatter};
 use std::str::FromStr;
 
 use crate::{
@@ -38,10 +39,14 @@ use crate::{
 ///
 /// More information about schema names can be found in the
 /// [Avro specification](https://avro.apache.org/docs/++version++/specification/#names)
-#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[derive(Clone, Hash, PartialEq, Eq)]
 pub struct Name {
-    pub name: String,
-    pub namespace: Namespace,
+    /// The full name
+    namespace_and_name: String,
+    /// Start byte of the name part
+    ///
+    /// If this is zero, then there is no namespace.
+    index_of_name: usize,
 }
 
 /// Represents the aliases for Named Schema
@@ -52,72 +57,90 @@ pub type Names = HashMap<Name, Schema>;
 pub type NamesRef<'a> = HashMap<Name, &'a Schema>;
 /// Represents the namespace for Named Schema
 pub type Namespace = Option<String>;
+/// Represents the namespace for Named Schema
+pub type NamespaceRef<'a> = Option<&'a str>;
 
 impl Name {
     /// Create a new `Name`.
     /// Parses the optional `namespace` from the `name` string.
     /// `aliases` will not be defined.
-    pub fn new(name: &str) -> AvroResult<Self> {
-        let (name, namespace) = Name::get_name_and_namespace(name)?;
-        Ok(Self {
-            name,
-            namespace: namespace.filter(|ns| !ns.is_empty()),
-        })
+    pub fn new(name: impl Into<String> + AsRef<str>) -> AvroResult<Self> {
+        Self::new_with_enclosing_namespace(name, None)
     }
 
-    fn get_name_and_namespace(name: &str) -> AvroResult<(String, Namespace)> {
-        validate_schema_name(name)
+    /// Create a new `Name` using the namespace from `enclosing_namespace` if absent.
+    pub fn new_with_enclosing_namespace(
+        name: impl Into<String> + AsRef<str>,
+        enclosing_namespace: NamespaceRef,
+    ) -> AvroResult<Self> {
+        // Having both `Into<String>` and `AsRef<str>` allows optimal use in both of these cases:
+        // - `name` is a `String`. We can reuse the allocation when `enclosing_namespace` is `None`
+        //   or `name` already has a namespace.
+        // - `name` is a `str`. With only `Into<String` we need an extra allocation in the case `name`
+        //   doesn't have namespace and `enclosing_namespace` is `Some`. Having `AsRef<str>` allows
+        //   skipping that allocation.
+        let name_ref = name.as_ref();
+        let index_of_name = validate_schema_name(name_ref)?;
+
+        if index_of_name == 0
+            && let Some(namespace) = enclosing_namespace
+            && !namespace.is_empty()
+        {
+            validate_namespace(namespace)?;
+            Ok(Self {
+                namespace_and_name: format!("{namespace}.{name_ref}"),
+                index_of_name: namespace.len() + 1,
+            })
+        } else if index_of_name == 1 {
+            // Name has a leading dot
+            Ok(Self {
+                namespace_and_name: name.as_ref()[1..].into(),
+                index_of_name: 0,
+            })
+        } else {
+            Ok(Self {
+                namespace_and_name: name.into(),
+                index_of_name,
+            })
+        }
     }
 
     /// Parse a `serde_json::Value` into a `Name`.
     pub(crate) fn parse(
         complex: &Map<String, Value>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Self> {
-        let (name, namespace_from_name) = complex
-            .name()
-            .map(|name| Name::get_name_and_namespace(name.as_str()).unwrap())
-            .ok_or(Details::GetNameField)?;
-        // FIXME Reading name from the type is wrong ! The name there is just a metadata (AVRO-3430)
-        let type_name = match complex.get("type") {
-            Some(Value::Object(complex_type)) => complex_type.name().or(None),
-            _ => None,
-        };
-
-        let namespace = namespace_from_name
-            .or_else(|| {
-                complex
-                    .string("namespace")
-                    .or_else(|| enclosing_namespace.clone())
-            })
-            .filter(|ns| !ns.is_empty());
+        let name_field = complex.name().ok_or(Details::GetNameField)?;
+        Self::new_with_enclosing_namespace(
+            name_field,
+            complex.string("namespace").or(enclosing_namespace),
+        )
+    }
 
-        if let Some(ref ns) = namespace {
-            validate_namespace(ns)?;
-        }
+    pub fn name(&self) -> &str {
+        &self.namespace_and_name[self.index_of_name..]
+    }
 
-        Ok(Self {
-            name: type_name.unwrap_or(name),
-            namespace,
-        })
+    pub fn namespace(&self) -> NamespaceRef<'_> {
+        if self.index_of_name == 0 {
+            None
+        } else {
+            Some(&self.namespace_and_name[..(self.index_of_name - 1)])
+        }
     }
 
     /// Return the `fullname` of this `Name`
     ///
     /// More information about fullnames can be found in the
     /// [Avro specification](https://avro.apache.org/docs/++version++/specification/#names)
-    pub fn fullname(&self, default_namespace: Namespace) -> String {
-        if self.name.contains('.') {
-            self.name.clone()
+    pub fn fullname(&self, enclosing_namespace: NamespaceRef) -> String {
+        if self.index_of_name == 0
+            && let Some(namespace) = enclosing_namespace
+            && !namespace.is_empty()
+        {
+            format!("{namespace}.{}", self.namespace_and_name)
         } else {
-            let namespace = self.namespace.clone().or(default_namespace);
-
-            match namespace {
-                Some(ref namespace) if !namespace.is_empty() => {
-                    format!("{}.{}", namespace, self.name)
-                }
-                _ => self.name.clone(),
-            }
+            self.namespace_and_name.clone()
         }
     }
 
@@ -126,22 +149,39 @@ impl Name {
     /// ```
     /// # use apache_avro::{Error, schema::Name};
     /// assert_eq!(
-    ///     Name::new("some_name")?.fully_qualified_name(&Some("some_namespace".into())),
+    ///     Name::new("some_name")?.fully_qualified_name(Some("some_namespace")).into_owned(),
     ///     Name::new("some_namespace.some_name")?
     /// );
     /// assert_eq!(
-    ///     Name::new("some_namespace.some_name")?.fully_qualified_name(&Some("other_namespace".into())),
+    ///     Name::new("some_namespace.some_name")?.fully_qualified_name(Some("other_namespace")).into_owned(),
     ///     Name::new("some_namespace.some_name")?
     /// );
     /// # Ok::<(), Error>(())
     /// ```
-    pub fn fully_qualified_name(&self, enclosing_namespace: &Namespace) -> Name {
-        Name {
-            name: self.name.clone(),
-            namespace: self
-                .namespace
-                .clone()
-                .or_else(|| enclosing_namespace.clone().filter(|ns| !ns.is_empty())),
+    pub fn fully_qualified_name(&self, enclosing_namespace: NamespaceRef) -> Cow<'_, Name> {
+        if self.index_of_name == 0
+            && let Some(namespace) = enclosing_namespace
+            && !namespace.is_empty()
+        {
+            Cow::Owned(Self {
+                namespace_and_name: format!("{namespace}.{}", self.namespace_and_name),
+                index_of_name: namespace.len() + 1,
+            })
+        } else {
+            Cow::Borrowed(self)
+        }
+    }
+
+    /// Create an empty name.
+    ///
+    /// This name is invalid and should never be used anywhere! The only valid use is filling
+    /// a `Name` field that will not be used.
+    ///
+    /// Using this name will cause a panic.
+    pub(crate) fn invalid_empty_name() -> Self {
+        Self {
+            namespace_and_name: String::new(),
+            index_of_name: usize::MAX,
         }
     }
 }
@@ -170,9 +210,22 @@ impl FromStr for Name {
     }
 }
 
-impl fmt::Display for Name {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_str(&self.fullname(None)[..])
+impl Debug for Name {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let mut debug = f.debug_struct("Name");
+        debug.field("name", &self.name());
+        if self.index_of_name != 0 {
+            debug.field("namespace", &self.namespace());
+            debug.finish()
+        } else {
+            debug.finish_non_exhaustive()
+        }
+    }
+}
+
+impl Display for Name {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        f.write_str(&self.namespace_and_name)
     }
 }
 
@@ -184,7 +237,7 @@ impl<'de> Deserialize<'de> for Name {
         Value::deserialize(deserializer).and_then(|value| {
             use serde::de::Error;
             if let Value::Object(json) = value {
-                Name::parse(&json, &None).map_err(Error::custom)
+                Name::parse(&json, None).map_err(Error::custom)
             } else {
                 Err(Error::custom(format!("Expected a JSON object: {value:?}")))
             }
@@ -203,18 +256,18 @@ impl Alias {
     }
 
     pub fn name(&self) -> &str {
-        &self.0.name
+        self.0.name()
     }
 
-    pub fn namespace(&self) -> &Namespace {
-        &self.0.namespace
+    pub fn namespace(&self) -> NamespaceRef<'_> {
+        self.0.namespace()
     }
 
-    pub fn fullname(&self, default_namespace: Namespace) -> String {
-        self.0.fullname(default_namespace)
+    pub fn fullname(&self, enclosing_namespace: NamespaceRef) -> String {
+        self.0.fullname(enclosing_namespace)
     }
 
-    pub fn fully_qualified_name(&self, default_namespace: &Namespace) -> Name {
+    pub fn fully_qualified_name(&self, default_namespace: NamespaceRef) -> Cow<'_, Name> {
         self.0.fully_qualified_name(default_namespace)
     }
 }
@@ -263,8 +316,8 @@ mod tests {
     /// Zero-length namespace is considered as no-namespace.
     fn test_namespace_from_name_with_empty_value() -> TestResult {
         let name = Name::new(".name")?;
-        assert_eq!(name.name, "name");
-        assert_eq!(name.namespace, None);
+        assert_eq!(name.namespace_and_name, "name");
+        assert_eq!(name.index_of_name, 0);
 
         Ok(())
     }
diff --git a/avro/src/schema/parser.rs b/avro/src/schema/parser.rs
index 6011d82..0f89018 100644
--- a/avro/src/schema/parser.rs
+++ b/avro/src/schema/parser.rs
@@ -18,7 +18,7 @@
 use crate::error::Details;
 use crate::schema::{
     Alias, Aliases, ArraySchema, DecimalMetadata, DecimalSchema, EnumSchema, FixedSchema,
-    MapSchema, Name, Names, Namespace, Precision, RecordField, RecordSchema, Scale, Schema,
+    MapSchema, Name, Names, NamespaceRef, Precision, RecordField, RecordSchema, Scale, Schema,
     SchemaKind, UnionSchema, UuidSchema,
 };
 use crate::types;
@@ -61,7 +61,7 @@ impl Parser {
     /// Create a `Schema` from a string representing a JSON Avro schema.
     pub(super) fn parse_str(&mut self, input: &str) -> AvroResult<Schema> {
         let value = serde_json::from_str(input).map_err(Details::ParseSchemaJson)?;
-        self.parse(&value, &None)
+        self.parse(&value, None)
     }
 
     /// Create an array of `Schema`s from an iterator of JSON Avro schemas.
@@ -94,7 +94,7 @@ impl Parser {
                 .input_schemas
                 .remove_entry(&next_name)
                 .expect("Key unexpectedly missing");
-            let parsed = self.parse(&value, &None)?;
+            let parsed = self.parse(&value, None)?;
             self.parsed_schemas
                 .insert(self.get_schema_type_name(name, value), parsed);
         }
@@ -105,7 +105,7 @@ impl Parser {
     pub(super) fn parse(
         &mut self,
         value: &Value,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         match *value {
             Value::String(ref t) => self.parse_known_schema(t.as_str(), enclosing_namespace),
@@ -119,7 +119,7 @@ impl Parser {
     fn parse_known_schema(
         &mut self,
         name: &str,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         match name {
             "null" => Ok(Schema::Null),
@@ -147,7 +147,7 @@ impl Parser {
     pub(super) fn fetch_schema_ref(
         &mut self,
         name: &str,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         fn get_schema_ref(parsed: &Schema) -> Schema {
             match parsed {
@@ -160,8 +160,7 @@ impl Parser {
             }
         }
 
-        let name = Name::new(name)?;
-        let fully_qualified_name = name.fully_qualified_name(enclosing_namespace);
+        let fully_qualified_name = Name::new_with_enclosing_namespace(name, enclosing_namespace)?;
 
         if self.parsed_schemas.contains_key(&fully_qualified_name) {
             return Ok(Schema::Ref {
@@ -173,9 +172,11 @@ impl Parser {
         }
 
         // For good error reporting we add this check
-        match name.name.as_str() {
+        match fully_qualified_name.name() {
             "record" | "enum" | "fixed" => {
-                return Err(Details::InvalidSchemaRecord(name.to_string()).into());
+                return Err(
+                    Details::InvalidSchemaRecord(fully_qualified_name.name().to_string()).into(),
+                );
             }
             _ => (),
         }
@@ -194,9 +195,11 @@ impl Parser {
             })?;
 
         // parsing a full schema from inside another schema. Other full schema will not inherit namespace
-        let parsed = self.parse(&value, &None)?;
-        self.parsed_schemas
-            .insert(self.get_schema_type_name(name, value), parsed.clone());
+        let parsed = self.parse(&value, None)?;
+        self.parsed_schemas.insert(
+            self.get_schema_type_name(fully_qualified_name, value),
+            parsed.clone(),
+        );
 
         Ok(get_schema_ref(&parsed))
     }
@@ -248,13 +251,13 @@ impl Parser {
     pub(super) fn parse_complex(
         &mut self,
         complex: &Map<String, Value>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         // Try to parse this as a native complex type.
         fn parse_as_native_complex(
             complex: &Map<String, Value>,
             parser: &mut Parser,
-            enclosing_namespace: &Namespace,
+            enclosing_namespace: NamespaceRef,
         ) -> AvroResult<Schema> {
             match complex.get("type") {
                 Some(value) => match value {
@@ -476,11 +479,11 @@ impl Parser {
         self.resolving_schemas
             .insert(name.clone(), resolving_schema.clone());
 
-        let namespace = &name.namespace;
+        let namespace = name.namespace();
 
         if let Some(aliases) = aliases {
             aliases.iter().for_each(|alias| {
-                let alias_fullname = alias.fully_qualified_name(namespace);
+                let alias_fullname = alias.fully_qualified_name(namespace).into_owned();
                 self.resolving_schemas
                     .insert(alias_fullname, resolving_schema.clone());
             });
@@ -499,13 +502,14 @@ impl Parser {
             .insert(fully_qualified_name.clone(), schema.clone());
         self.resolving_schemas.remove(fully_qualified_name);
 
-        let namespace = &fully_qualified_name.namespace;
+        let namespace = fully_qualified_name.namespace();
 
         if let Some(aliases) = aliases {
             aliases.iter().for_each(|alias| {
                 let alias_fullname = alias.fully_qualified_name(namespace);
                 self.resolving_schemas.remove(&alias_fullname);
-                self.parsed_schemas.insert(alias_fullname, schema.clone());
+                self.parsed_schemas
+                    .insert(alias_fullname.into_owned(), schema.clone());
             });
         }
     }
@@ -514,13 +518,12 @@ impl Parser {
     fn get_already_seen_schema(
         &self,
         complex: &Map<String, Value>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Option<&Schema> {
         match complex.get("type") {
             Some(Value::String(typ)) => {
-                let name = Name::new(typ.as_str())
-                    .unwrap()
-                    .fully_qualified_name(enclosing_namespace);
+                let name =
+                    Name::new_with_enclosing_namespace(typ.as_str(), enclosing_namespace).unwrap();
                 self.resolving_schemas
                     .get(&name)
                     .or_else(|| self.parsed_schemas.get(&name))
@@ -533,7 +536,7 @@ impl Parser {
     fn parse_record(
         &mut self,
         complex: &Map<String, Value>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         let fields_opt = complex.get("fields");
 
@@ -545,7 +548,7 @@ impl Parser {
 
         let fully_qualified_name = Name::parse(complex, enclosing_namespace)?;
         let aliases =
-            self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace);
+            self.fix_aliases_namespace(complex.aliases(), fully_qualified_name.namespace());
 
         let mut lookup = BTreeMap::new();
 
@@ -607,7 +610,7 @@ impl Parser {
     fn parse_enum(
         &mut self,
         complex: &Map<String, Value>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         let symbols_opt = complex.get("symbols");
 
@@ -619,7 +622,7 @@ impl Parser {
 
         let fully_qualified_name = Name::parse(complex, enclosing_namespace)?;
         let aliases =
-            self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace);
+            self.fix_aliases_namespace(complex.aliases(), fully_qualified_name.namespace());
 
         let symbols: Vec<String> = symbols_opt
             .and_then(|v| v.as_array())
@@ -684,7 +687,7 @@ impl Parser {
     fn parse_array(
         &mut self,
         complex: &Map<String, Value>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         let items = complex
             .get("items")
@@ -721,7 +724,7 @@ impl Parser {
     fn parse_map(
         &mut self,
         complex: &Map<String, Value>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         let types = complex
             .get("values")
@@ -759,7 +762,7 @@ impl Parser {
     fn parse_union(
         &mut self,
         items: &[Value],
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         items
             .iter()
@@ -787,7 +790,7 @@ impl Parser {
     fn parse_fixed(
         &mut self,
         complex: &Map<String, Value>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> AvroResult<Schema> {
         let size_opt = complex.get("size");
         if size_opt.is_none()
@@ -810,7 +813,7 @@ impl Parser {
 
         let fully_qualified_name = Name::parse(complex, enclosing_namespace)?;
         let aliases =
-            self.fix_aliases_namespace(complex.aliases(), &fully_qualified_name.namespace);
+            self.fix_aliases_namespace(complex.aliases(), fully_qualified_name.namespace());
 
         let schema = Schema::Fixed(FixedSchema {
             name: fully_qualified_name.clone(),
@@ -833,7 +836,7 @@ impl Parser {
     fn fix_aliases_namespace(
         &self,
         aliases: Option<Vec<String>>,
-        namespace: &Namespace,
+        namespace: NamespaceRef,
     ) -> Aliases {
         aliases.map(|aliases| {
             aliases
@@ -856,7 +859,7 @@ impl Parser {
     fn get_schema_type_name(&self, name: Name, value: Value) -> Name {
         match value.get("type") {
             Some(Value::Object(complex_type)) => match complex_type.name() {
-                Some(name) => Name::new(name.as_str()).unwrap(),
+                Some(name) => Name::new(name).unwrap(),
                 _ => name,
             },
             _ => name,
diff --git a/avro/src/schema/record/field.rs b/avro/src/schema/record/field.rs
index 043a51c..11326e1 100644
--- a/avro/src/schema/record/field.rs
+++ b/avro/src/schema/record/field.rs
@@ -89,10 +89,10 @@ impl RecordField {
     ) -> AvroResult<Self> {
         let name = field.name().ok_or(Details::GetNameFieldFromRecord)?;
 
-        validate_record_field_name(&name)?;
+        validate_record_field_name(name)?;
 
         let ty = field.get("type").ok_or(Details::GetRecordFieldTypeField)?;
-        let schema = parser.parse(ty, &enclosing_record.namespace)?;
+        let schema = parser.parse(ty, enclosing_record.namespace())?;
 
         if let Some(logical_type) = field.get("logicalType") {
             warn!(
@@ -103,7 +103,7 @@ impl RecordField {
         let default = field.get("default").cloned();
         Self::resolve_default_value(
             &schema,
-            &name,
+            name,
             &enclosing_record.fullname(None),
             parser.get_parsed_schemas(),
             &default,
@@ -123,7 +123,7 @@ impl RecordField {
             .unwrap_or_default();
 
         Ok(RecordField {
-            name,
+            name: name.into(),
             doc: field.doc(),
             default,
             aliases,
@@ -147,7 +147,7 @@ impl RecordField {
                     let resolved = schemas.iter().any(|schema| {
                         avro_value
                             .to_owned()
-                            .resolve_internal(schema, names, &schema.namespace(), &None)
+                            .resolve_internal(schema, names, schema.namespace(), &None)
                             .is_ok()
                     });
 
@@ -165,7 +165,7 @@ impl RecordField {
                 }
                 _ => {
                     let resolved = avro_value
-                        .resolve_internal(field_schema, names, &field_schema.namespace(), &None)
+                        .resolve_internal(field_schema, names, field_schema.namespace(), &None)
                         .is_ok();
 
                     if !resolved {
@@ -250,10 +250,7 @@ mod tests {
             .schema(Schema::Union(UnionSchema::new(vec![
                 Schema::Null,
                 Schema::Ref {
-                    name: Name {
-                        name: "LongList".to_owned(),
-                        namespace: None,
-                    },
+                    name: Name::new("LongList")?,
                 },
             ])?))
             .build();
diff --git a/avro/src/schema/resolve.rs b/avro/src/schema/resolve.rs
index 9274e61..449436b 100644
--- a/avro/src/schema/resolve.rs
+++ b/avro/src/schema/resolve.rs
@@ -17,8 +17,8 @@
 
 use crate::error::Details;
 use crate::schema::{
-    DecimalSchema, EnumSchema, FixedSchema, InnerDecimalSchema, NamesRef, Namespace, RecordSchema,
-    UnionSchema, UuidSchema,
+    DecimalSchema, EnumSchema, FixedSchema, InnerDecimalSchema, NamesRef, NamespaceRef,
+    RecordSchema, UnionSchema, UuidSchema,
 };
 use crate::{AvroResult, Error, Schema};
 use std::collections::HashMap;
@@ -51,7 +51,7 @@ impl<'s> ResolvedSchema<'s> {
     /// These schemas will be resolved in order, so references to schemas later in the
     /// list is not supported.
     pub fn new_with_schemata(schemata: Vec<&'s Schema>) -> AvroResult<Self> {
-        Self::new_with_known_schemata(schemata, &None, &HashMap::new())
+        Self::new_with_known_schemata(schemata, None, &HashMap::new())
     }
 
     /// Creates `ResolvedSchema` with some already known schemas.
@@ -59,7 +59,7 @@ impl<'s> ResolvedSchema<'s> {
     /// Those schemata would be used to resolve references if needed.
     pub fn new_with_known_schemata<'n>(
         schemata_to_resolve: Vec<&'s Schema>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
         known_schemata: &'n NamesRef<'n>,
     ) -> AvroResult<Self> {
         let mut names = HashMap::new();
@@ -116,7 +116,7 @@ impl ResolvedOwnedSchema {
                 root_schema,
                 names_builder: |schema: &Schema| {
                     let mut names = HashMap::new();
-                    resolve_names(schema, &mut names, &None, &HashMap::new())?;
+                    resolve_names(schema, &mut names, None, &HashMap::new())?;
                     Ok::<_, Error>(names)
                 },
             }
@@ -146,7 +146,7 @@ impl TryFrom<Schema> for ResolvedOwnedSchema {
 pub fn resolve_names<'s, 'n>(
     schema: &'s Schema,
     names: &mut NamesRef<'s>,
-    enclosing_namespace: &Namespace,
+    enclosing_namespace: NamespaceRef,
     known_schemata: &NamesRef<'n>,
 ) -> AvroResult<()> {
     match schema {
@@ -170,7 +170,7 @@ pub fn resolve_names<'s, 'n>(
             ..
         })
         | Schema::Duration(FixedSchema { name, .. }) => {
-            let fully_qualified_name = name.fully_qualified_name(enclosing_namespace);
+            let fully_qualified_name = name.fully_qualified_name(enclosing_namespace).into_owned();
             if names.contains_key(&fully_qualified_name)
                 || known_schemata.contains_key(&fully_qualified_name)
             {
@@ -181,16 +181,21 @@ pub fn resolve_names<'s, 'n>(
             }
         }
         Schema::Record(RecordSchema { name, fields, .. }) => {
-            let fully_qualified_name = name.fully_qualified_name(enclosing_namespace);
+            let fully_qualified_name = name.fully_qualified_name(enclosing_namespace).into_owned();
             if names.contains_key(&fully_qualified_name)
                 || known_schemata.contains_key(&fully_qualified_name)
             {
                 Err(Details::AmbiguousSchemaDefinition(fully_qualified_name).into())
             } else {
-                let record_namespace = fully_qualified_name.namespace.clone();
+                let record_namespace = fully_qualified_name.namespace().map(ToString::to_string);
                 names.insert(fully_qualified_name, schema);
                 for field in fields {
-                    resolve_names(&field.schema, names, &record_namespace, known_schemata)?
+                    resolve_names(
+                        &field.schema,
+                        names,
+                        record_namespace.as_deref(),
+                        known_schemata,
+                    )?
                 }
                 Ok(())
             }
@@ -202,7 +207,7 @@ pub fn resolve_names<'s, 'n>(
             {
                 Ok(())
             } else {
-                Err(Details::SchemaResolutionError(fully_qualified_name).into())
+                Err(Details::SchemaResolutionError(fully_qualified_name.into_owned()).into())
             }
         }
         _ => Ok(()),
@@ -212,7 +217,7 @@ pub fn resolve_names<'s, 'n>(
 pub fn resolve_names_with_schemata<'s, 'n>(
     schemata: impl IntoIterator<Item = &'s Schema>,
     names: &mut NamesRef<'s>,
-    enclosing_namespace: &Namespace,
+    enclosing_namespace: NamespaceRef,
     known_schemata: &NamesRef<'n>,
 ) -> AvroResult<()> {
     for schema in schemata {
@@ -265,7 +270,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "space.inner_record_name"] {
+        for s in ["space.record_name", "space.inner_record_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -306,7 +311,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "space.inner_record_name"] {
+        for s in ["space.record_name", "space.inner_record_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -342,7 +347,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "space.inner_enum_name"] {
+        for s in ["space.record_name", "space.inner_enum_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -378,7 +383,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "space.inner_enum_name"] {
+        for s in ["space.record_name", "space.inner_enum_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -414,7 +419,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "space.inner_fixed_name"] {
+        for s in ["space.record_name", "space.inner_fixed_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -450,7 +455,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "space.inner_fixed_name"] {
+        for s in ["space.record_name", "space.inner_fixed_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -492,7 +497,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "inner_space.inner_record_name"] {
+        for s in ["space.record_name", "inner_space.inner_record_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -529,7 +534,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "inner_space.inner_enum_name"] {
+        for s in ["space.record_name", "inner_space.inner_enum_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -566,7 +571,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "inner_space.inner_fixed_name"] {
+        for s in ["space.record_name", "inner_space.inner_fixed_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -619,7 +624,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 3);
-        for s in &[
+        for s in [
             "space.record_name",
             "space.middle_record_name",
             "space.inner_record_name",
@@ -677,7 +682,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 3);
-        for s in &[
+        for s in [
             "space.record_name",
             "middle_namespace.middle_record_name",
             "middle_namespace.inner_record_name",
@@ -736,7 +741,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 3);
-        for s in &[
+        for s in [
             "space.record_name",
             "middle_namespace.middle_record_name",
             "inner_namespace.inner_record_name",
@@ -781,7 +786,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "space.in_array_record"] {
+        for s in ["space.record_name", "space.in_array_record"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -822,7 +827,7 @@ mod tests {
         let schema = Schema::parse_str(schema)?;
         let rs = ResolvedSchema::new(&schema)?;
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "space.in_map_record"] {
+        for s in ["space.record_name", "space.in_map_record"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -861,7 +866,7 @@ mod tests {
 
         // confirm we have expected 2 full-names
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "inner_space.inner_enum_name"] {
+        for s in ["space.record_name", "inner_space.inner_enum_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -905,7 +910,7 @@ mod tests {
 
         // confirm we have expected 2 full-names
         assert_eq!(rs.get_names().len(), 2);
-        for s in &["space.record_name", "inner_space.inner_fixed_name"] {
+        for s in ["space.record_name", "inner_space.inner_fixed_name"] {
             assert!(rs.get_names().contains_key(&Name::new(s)?));
         }
 
@@ -1015,7 +1020,7 @@ mod tests {
         let mut known_schemata: NamesRef = HashMap::default();
         known_schemata.insert("duplicated_name".try_into()?, &Schema::Boolean);
 
-        let result = ResolvedSchema::new_with_known_schemata(vec![&schema], &None, &known_schemata)
+        let result = ResolvedSchema::new_with_known_schemata(vec![&schema], None, &known_schemata)
             .unwrap_err();
 
         assert_eq!(
diff --git a/avro/src/schema/union.rs b/avro/src/schema/union.rs
index 938e765..31affe7 100644
--- a/avro/src/schema/union.rs
+++ b/avro/src/schema/union.rs
@@ -17,7 +17,7 @@
 
 use crate::error::Details;
 use crate::schema::{
-    DecimalSchema, InnerDecimalSchema, Name, Namespace, Schema, SchemaKind, UuidSchema,
+    DecimalSchema, InnerDecimalSchema, Name, NamespaceRef, Schema, SchemaKind, UuidSchema,
 };
 use crate::types;
 use crate::{AvroResult, Error};
@@ -88,7 +88,7 @@ impl UnionSchema {
         &self,
         value: &types::Value,
         known_schemata: Option<&HashMap<Name, S>>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Option<(usize, &Schema)> {
         let known_schemata_if_none = HashMap::new();
         let known_schemata = known_schemata.unwrap_or(&known_schemata_if_none);
@@ -101,11 +101,7 @@ impl UnionSchema {
                 let kind = schema.discriminant();
                 // Maps and arrays need to be checked if they actually match the value
                 if kind == SchemaKind::Map || kind == SchemaKind::Array {
-                    let namespace = if schema.namespace().is_some() {
-                        &schema.namespace()
-                    } else {
-                        enclosing_namespace
-                    };
+                    let namespace = schema.namespace().or(enclosing_namespace);
 
                     // TODO: Do this without the clone
                     value
@@ -129,11 +125,7 @@ impl UnionSchema {
                     s_kind == kind || s_kind == SchemaKind::Ref
                 })
                 .find(|(_i, schema)| {
-                    let namespace = if schema.namespace().is_some() {
-                        &schema.namespace()
-                    } else {
-                        enclosing_namespace
-                    };
+                    let namespace = schema.namespace().or(enclosing_namespace);
 
                     // TODO: Do this without the clone
                     value
@@ -151,11 +143,7 @@ impl UnionSchema {
             (None, None) => {
                 // Slow path, check if value can be promoted to any of the types in the union
                 self.schemas.iter().enumerate().find(|(_i, schema)| {
-                    let namespace = if schema.namespace().is_some() {
-                        &schema.namespace()
-                    } else {
-                        enclosing_namespace
-                    };
+                    let namespace = schema.namespace().or(enclosing_namespace);
 
                     // TODO: Do this without the clone
                     value
@@ -575,7 +563,7 @@ mod tests {
 
         assert!(
             union
-                .find_schema_with_known_schemata(&value, None::<&HashMap<Name, Schema>>, &None)
+                .find_schema_with_known_schemata(&value, None::<&HashMap<Name, Schema>>, None)
                 .is_none()
         );
 
@@ -588,7 +576,7 @@ mod tests {
         let value = Value::Int(42);
 
         assert_eq!(
-            union.find_schema_with_known_schemata(&value, None::<&HashMap<Name, Schema>>, &None),
+            union.find_schema_with_known_schemata(&value, None::<&HashMap<Name, Schema>>, None),
             Some((0, &Schema::Long))
         );
 
@@ -609,7 +597,7 @@ mod tests {
         let value = Value::Fixed(16, vec![0; 16]);
 
         assert_eq!(
-            union.find_schema_with_known_schemata(&value, None::<&HashMap<Name, Schema>>, &None),
+            union.find_schema_with_known_schemata(&value, None::<&HashMap<Name, Schema>>, None),
             Some((0, &uuid))
         );
 
diff --git a/avro/src/schema_compatibility.rs b/avro/src/schema_compatibility.rs
index b172d94..8baafd3 100644
--- a/avro/src/schema_compatibility.rs
+++ b/avro/src/schema_compatibility.rs
@@ -182,11 +182,11 @@ impl Checker {
         // Compare unqualified names if the schemas have them
         if let Some(w_name) = writers_schema.name()
             && let Some(r_name) = readers_schema.name()
-            && w_name.name != r_name.name
+            && w_name.name() != r_name.name()
         {
             return Err(CompatibilityError::NameMismatch {
-                writer_name: w_name.name.clone(),
-                reader_name: r_name.name.clone(),
+                writer_name: w_name.name().into(),
+                reader_name: r_name.name().into(),
             });
         }
 
diff --git a/avro/src/schema_equality.rs b/avro/src/schema_equality.rs
index aaa2e7e..0143a22 100644
--- a/avro/src/schema_equality.rs
+++ b/avro/src/schema_equality.rs
@@ -696,10 +696,7 @@ mod tests {
         let string = Schema::Uuid(UuidSchema::String);
         let bytes = Schema::Uuid(UuidSchema::Bytes);
         let mut fixed_schema = FixedSchema {
-            name: Name {
-                name: "some_name".to_string(),
-                namespace: None,
-            },
+            name: Name::new("some_name")?,
             aliases: None,
             doc: None,
             size: 16,
diff --git a/avro/src/serde/derive.rs b/avro/src/serde/derive.rs
index e01f112..d979962 100644
--- a/avro/src/serde/derive.rs
+++ b/avro/src/serde/derive.rs
@@ -17,7 +17,7 @@
 
 use crate::Schema;
 use crate::schema::{
-    FixedSchema, Name, Namespace, RecordField, RecordSchema, UnionSchema, UuidSchema,
+    FixedSchema, Name, NamespaceRef, RecordField, RecordSchema, UnionSchema, UuidSchema,
 };
 use std::borrow::Cow;
 use std::collections::{HashMap, HashSet};
@@ -190,15 +190,15 @@ use std::collections::{HashMap, HashSet};
 ///
 /// 1. In combination with `#[serde(with = "path::to::module)]`
 ///
-///    To get the schema, it will call the functions `fn get_schema_in_ctxt(&mut HashSet<Name>, &Namespace) -> Schema`
-///    and `fn get_record_fields_in_ctxt(usize, &mut HashSet<Name>, &Namespace) -> Option<Vec<RecordField>>` in the module provided
+///    To get the schema, it will call the functions `fn get_schema_in_ctxt(&mut HashSet<Name>, NamespaceRef) -> Schema`
+///    and `fn get_record_fields_in_ctxt(usize, &mut HashSet<Name>, NamespaceRef) -> Option<Vec<RecordField>>` in the module provided
 ///    to the Serde attribute. See [`AvroSchemaComponent`] for details on how to implement those
 ///    functions.
 ///
 /// 2. By providing a function directly, `#[avro(with = some_fn)]`.
 ///
 ///    To get the schema, it will call the function provided. It must have the signature
-///    `fn(&mut HashSet<Name>, &Namespace) -> Schema`. When this is used for a `transparent` struct, the
+///    `fn(&mut HashSet<Name>, NamespaceRef) -> Schema`. When this is used for a `transparent` struct, the
 ///    default implementation of [`AvroSchemaComponent::get_record_fields_in_ctxt`] will be used.
 ///    This is only recommended for primitive types, as the default implementation cannot be efficiently
 ///    implemented for complex types.
@@ -223,16 +223,16 @@ pub trait AvroSchema {
 ///
 /// For example, you have a custom integer type:
 /// ```
-/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, Namespace, RecordField}};
+/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, NamespaceRef, RecordField}};
 /// # use std::collections::HashSet;
 /// // Make sure to implement `Serialize` and `Deserialize` to use the right serialization methods
 /// pub struct U24([u8; 3]);
 /// impl AvroSchemaComponent for U24 {
-///     fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
+///     fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema {
 ///         Schema::Int
 ///     }
 ///
-///     fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+///     fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
 ///         None // A Schema::Int is not a Schema::Record so there are no fields to return
 ///     }
 ///
@@ -248,18 +248,18 @@ pub trait AvroSchema {
 /// To construct a schema for a type is "transparent", such as for smart pointers, simply
 /// pass through the arguments to the inner type:
 /// ```
-/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, Namespace, RecordField}};
+/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, NamespaceRef, RecordField}};
 /// # use serde::{Serialize, Deserialize};
 /// # use std::collections::HashSet;
 /// #[derive(Serialize, Deserialize)]
 /// #[serde(transparent)] // This attribute is important for all passthrough implementations!
 /// pub struct Transparent<T>(T);
 /// impl<T: AvroSchemaComponent> AvroSchemaComponent for Transparent<T> {
-///     fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema {
+///     fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Schema {
 ///         T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
 ///     }
 ///
-///     fn get_record_fields_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> {
+///     fn get_record_fields_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Option<Vec<RecordField>> {
 ///         T::get_record_fields_in_ctxt(named_schemas, enclosing_namespace)
 ///     }
 ///
@@ -283,7 +283,7 @@ pub trait AvroSchema {
 ///  - Implement `field_default()` if you want to use `#[serde(skip_serializing{,_if})]`.
 ///
 /// ```
-/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, Namespace, RecordField, RecordSchema}};
+/// # use apache_avro::{Schema, serde::{AvroSchemaComponent}, schema::{Name, NamespaceRef, RecordField, RecordSchema}};
 /// # use serde::{Serialize, Deserialize};
 /// # use std::{time::Duration, collections::HashSet};
 /// pub struct Foo {
@@ -293,13 +293,13 @@ pub trait AvroSchema {
 /// }
 ///
 /// impl AvroSchemaComponent for Foo {
-///     fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema {
+///     fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Schema {
 ///         // Create the fully qualified name for your type given the enclosing namespace
-///         let name = Name::new("Foo").unwrap().fully_qualified_name(enclosing_namespace);
+///         let name = Name::new_with_enclosing_namespace("Foo", enclosing_namespace).expect("Name is valid");
 ///         if named_schemas.contains(&name) {
 ///             Schema::Ref { name }
 ///         } else {
-///             let enclosing_namespace = &name.namespace;
+///             let enclosing_namespace = name.namespace();
 ///             // Do this before you start creating the schema, as otherwise recursive types will cause infinite recursion.
 ///             named_schemas.insert(name.clone());
 ///             let schema = Schema::Record(RecordSchema::builder()
@@ -311,7 +311,7 @@ pub trait AvroSchema {
 ///         }
 ///     }
 ///
-///     fn get_record_fields_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> {
+///     fn get_record_fields_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Option<Vec<RecordField>> {
 ///         Some(vec![
 ///             RecordField::builder()
 ///                 .name("one")
@@ -338,7 +338,7 @@ pub trait AvroSchemaComponent {
     /// Get the schema for this component
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema;
 
     /// Get the fields of this schema if it is a record.
@@ -349,7 +349,7 @@ pub trait AvroSchemaComponent {
     /// implement this function when manually implementing this trait.
     fn get_record_fields_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Option<Vec<RecordField>> {
         get_record_fields_in_ctxt(named_schemas, enclosing_namespace, Self::get_schema_in_ctxt)
     }
@@ -370,8 +370,8 @@ pub trait AvroSchemaComponent {
 /// This is public so the derive macro can use it for `#[avro(with = ||)]` and `#[avro(with = path)]`
 pub fn get_record_fields_in_ctxt(
     named_schemas: &mut HashSet<Name>,
-    enclosing_namespace: &Namespace,
-    schema_fn: fn(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema,
+    enclosing_namespace: NamespaceRef,
+    schema_fn: fn(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Schema,
 ) -> Option<Vec<RecordField>> {
     let mut record = match schema_fn(named_schemas, enclosing_namespace) {
         Schema::Record(record) => record,
@@ -485,18 +485,18 @@ where
     T: AvroSchemaComponent + ?Sized,
 {
     fn get_schema() -> Schema {
-        T::get_schema_in_ctxt(&mut HashSet::default(), &None)
+        T::get_schema_in_ctxt(&mut HashSet::default(), None)
     }
 }
 
 macro_rules! impl_schema (
     ($type:ty, $variant_constructor:expr) => (
         impl AvroSchemaComponent for $type {
-            fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
+            fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema {
                 $variant_constructor
             }
 
-            fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+            fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
                 None
             }
         }
@@ -521,11 +521,11 @@ impl_schema!((), Schema::Null);
 macro_rules! impl_passthrough_schema (
     ($type:ty where T: AvroSchemaComponent + ?Sized $(+ $bound:tt)*) => (
         impl<T: AvroSchemaComponent $(+ $bound)* + ?Sized> AvroSchemaComponent for $type {
-            fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema {
+            fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Schema {
                 T::get_schema_in_ctxt(named_schemas, enclosing_namespace)
             }
 
-            fn get_record_fields_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Option<Vec<RecordField>> {
+            fn get_record_fields_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Option<Vec<RecordField>> {
                 T::get_record_fields_in_ctxt(named_schemas, enclosing_namespace)
             }
 
@@ -545,11 +545,11 @@ impl_passthrough_schema!(std::sync::Mutex<T> where T: AvroSchemaComponent + ?Siz
 macro_rules! impl_array_schema (
     ($type:ty where T: AvroSchemaComponent) => (
         impl<T: AvroSchemaComponent> AvroSchemaComponent for $type {
-            fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: &Namespace) -> Schema {
+            fn get_schema_in_ctxt(named_schemas: &mut HashSet<Name>, enclosing_namespace: NamespaceRef) -> Schema {
                 Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build()
             }
 
-            fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+            fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
                 None
             }
         }
@@ -567,12 +567,12 @@ where
 {
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
         Schema::array(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build()
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -583,12 +583,12 @@ where
 {
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
         Schema::map(T::get_schema_in_ctxt(named_schemas, enclosing_namespace)).build()
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -599,7 +599,7 @@ where
 {
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
         let variants = vec![
             Schema::Null,
@@ -611,7 +611,7 @@ where
         )
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
         None
     }
 
@@ -626,11 +626,10 @@ impl AvroSchemaComponent for core::time::Duration {
     /// This is a lossy conversion as this Avro type does not store the amount of nanoseconds.
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
-        let name = Name::new("duration")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
+        let name = Name::new_with_enclosing_namespace("duration", enclosing_namespace)
+            .expect("Name is valid");
         if named_schemas.contains(&name) {
             Schema::Ref { name }
         } else {
@@ -646,7 +645,7 @@ impl AvroSchemaComponent for core::time::Duration {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -657,11 +656,10 @@ impl AvroSchemaComponent for uuid::Uuid {
     /// The underlying schema is [`Schema::Fixed`] with a size of 16.
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
-        let name = Name::new("uuid")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
+        let name =
+            Name::new_with_enclosing_namespace("uuid", enclosing_namespace).expect("Name is valid");
         if named_schemas.contains(&name) {
             Schema::Ref { name }
         } else {
@@ -677,7 +675,7 @@ impl AvroSchemaComponent for uuid::Uuid {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -686,11 +684,10 @@ impl AvroSchemaComponent for u64 {
     /// The schema is [`Schema::Fixed`] of size 8 with the name `u64`.
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
-        let name = Name::new("u64")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
+        let name =
+            Name::new_with_enclosing_namespace("u64", enclosing_namespace).expect("Name is valid");
         if named_schemas.contains(&name) {
             Schema::Ref { name }
         } else {
@@ -706,7 +703,7 @@ impl AvroSchemaComponent for u64 {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -715,11 +712,10 @@ impl AvroSchemaComponent for u128 {
     /// The schema is [`Schema::Fixed`] of size 16 with the name `u128`.
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
-        let name = Name::new("u128")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
+        let name =
+            Name::new_with_enclosing_namespace("u128", enclosing_namespace).expect("Name is valid");
         if named_schemas.contains(&name) {
             Schema::Ref { name }
         } else {
@@ -735,7 +731,7 @@ impl AvroSchemaComponent for u128 {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
         None
     }
 }
@@ -744,11 +740,10 @@ impl AvroSchemaComponent for i128 {
     /// The schema is [`Schema::Fixed`] of size 16 with the name `i128`.
     fn get_schema_in_ctxt(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
-        let name = Name::new("i128")
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
+        let name =
+            Name::new_with_enclosing_namespace("i128", enclosing_namespace).expect("Name is valid");
         if named_schemas.contains(&name) {
             Schema::Ref { name }
         } else {
@@ -764,7 +759,7 @@ impl AvroSchemaComponent for i128 {
         }
     }
 
-    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Option<Vec<RecordField>> {
+    fn get_record_fields_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Option<Vec<RecordField>> {
         None
     }
 }
diff --git a/avro/src/serde/ser_schema/mod.rs b/avro/src/serde/ser_schema/mod.rs
index b49f059..50db278 100644
--- a/avro/src/serde/ser_schema/mod.rs
+++ b/avro/src/serde/ser_schema/mod.rs
@@ -27,7 +27,7 @@ use crate::{
 };
 use bigdecimal::BigDecimal;
 use serde::{Serialize, ser};
-use std::{borrow::Cow, cmp::Ordering, collections::HashMap, io::Write, str::FromStr};
+use std::{cmp::Ordering, collections::HashMap, io::Write, str::FromStr};
 
 const COLLECTION_SERIALIZER_ITEM_LIMIT: usize = 1024;
 const COLLECTION_SERIALIZER_DEFAULT_INIT_ITEM_CAPACITY: usize = 32;
@@ -629,13 +629,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
     }
 
     fn get_ref_schema(&self, name: &'s Name) -> Result<&'s Schema, Error> {
-        let full_name = match name.namespace {
-            Some(_) => Cow::Borrowed(name),
-            None => Cow::Owned(Name {
-                name: name.name.clone(),
-                namespace: self.enclosing_namespace.clone(),
-            }),
-        };
+        let full_name = name.fully_qualified_name(self.enclosing_namespace.as_deref());
 
         let ref_schema = self.names.get(full_name.as_ref()).copied();
 
@@ -795,7 +789,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
         };
 
         match schema {
-            Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name == "i128" => {
+            Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name() == "i128" => {
                 self.writer
                     .write_all(&value.to_le_bytes())
                     .map_err(Details::WriteBytes)?;
@@ -804,7 +798,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
             Schema::Union(union_schema) => {
                 for (i, variant_schema) in union_schema.schemas.iter().enumerate() {
                     match variant_schema {
-                        Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name == "i128" => {
+                        Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name() == "i128" => {
                             encode_int(i as i32, &mut *self.writer)?;
                             return self.serialize_i128_with_schema(value, variant_schema);
                         }
@@ -949,7 +943,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
                     i64::try_from(value).map_err(|cause| create_error(cause.to_string()))?;
                 encode_long(long_value, &mut self.writer)
             }
-            Schema::Fixed(fixed) if fixed.size == 8 && fixed.name.name == "u64" => {
+            Schema::Fixed(fixed) if fixed.size == 8 && fixed.name.name() == "u64" => {
                 self.writer
                     .write_all(&value.to_le_bytes())
                     .map_err(Details::WriteBytes)?;
@@ -972,7 +966,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
                             encode_int(i as i32, &mut *self.writer)?;
                             return self.serialize_u64_with_schema(value, variant_schema);
                         }
-                        Schema::Fixed(fixed) if fixed.size == 8 && fixed.name.name == "u64" => {
+                        Schema::Fixed(fixed) if fixed.size == 8 && fixed.name.name() == "u64" => {
                             encode_int(i as i32, &mut *self.writer)?;
                             return self.serialize_u64_with_schema(value, variant_schema);
                         }
@@ -998,7 +992,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
         };
 
         match schema {
-            Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name == "u128" => {
+            Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name() == "u128" => {
                 self.writer
                     .write_all(&value.to_le_bytes())
                     .map_err(Details::WriteBytes)?;
@@ -1007,7 +1001,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
             Schema::Union(union_schema) => {
                 for (i, variant_schema) in union_schema.schemas.iter().enumerate() {
                     match variant_schema {
-                        Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name == "u128" => {
+                        Schema::Fixed(fixed) if fixed.size == 16 && fixed.name.name() == "u128" => {
                             encode_int(i as i32, &mut *self.writer)?;
                             return self.serialize_u128_with_schema(value, variant_schema);
                         }
@@ -1108,7 +1102,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
 
         match schema {
             Schema::String | Schema::Bytes => self.write_bytes(String::from(value).as_bytes()),
-            Schema::Fixed(fixed) if fixed.size == 4 && fixed.name.name == "char" => {
+            Schema::Fixed(fixed) if fixed.size == 4 && fixed.name.name() == "char" => {
                 self.writer
                     .write_all(&u32::from(value).to_le_bytes())
                     .map_err(Details::WriteBytes)?;
@@ -1121,7 +1115,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
                             encode_int(i as i32, &mut *self.writer)?;
                             return self.serialize_char_with_schema(value, variant_schema);
                         }
-                        Schema::Fixed(fixed) if fixed.size == 4 && fixed.name.name == "char" => {
+                        Schema::Fixed(fixed) if fixed.size == 4 && fixed.name.name() == "char" => {
                             encode_int(i as i32, &mut *self.writer)?;
                             return self.serialize_char_with_schema(value, variant_schema);
                         }
@@ -1791,7 +1785,7 @@ impl<'s, W: Write> SchemaAwareWriteSerializer<'s, W> {
                 for (i, variant_schema) in union_schema.schemas.iter().enumerate() {
                     match variant_schema {
                         Schema::Record(inner)
-                            if inner.fields.len() == len && inner.name.name == name =>
+                            if inner.fields.len() == len && inner.name.name() == name =>
                         {
                             encode_int(i as i32, &mut *self.writer)?;
                             return self.serialize_struct_with_schema(name, len, variant_schema);
diff --git a/avro/src/serde/with.rs b/avro/src/serde/with.rs
index 9d236d1..29744e1 100644
--- a/avro/src/serde/with.rs
+++ b/avro/src/serde/with.rs
@@ -98,13 +98,14 @@ pub mod bytes {
 
     use serde::{Deserializer, Serializer};
 
+    use crate::schema::NamespaceRef;
     use crate::{
         Schema,
-        schema::{Name, Namespace, RecordField},
+        schema::{Name, RecordField},
     };
 
     /// Returns [`Schema::Bytes`]
-    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
+    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema {
         Schema::Bytes
     }
 
@@ -112,7 +113,7 @@ pub mod bytes {
     pub fn get_record_fields_in_ctxt(
         _: usize,
         _: &mut HashSet<Name>,
-        _: &Namespace,
+        _: NamespaceRef,
     ) -> Option<Vec<RecordField>> {
         None
     }
@@ -163,13 +164,14 @@ pub mod bytes_opt {
     use serde::{Deserializer, Serializer};
     use std::{borrow::Borrow, collections::HashSet};
 
+    use crate::schema::NamespaceRef;
     use crate::{
         Schema,
-        schema::{Name, Namespace, RecordField, UnionSchema},
+        schema::{Name, RecordField, UnionSchema},
     };
 
     /// Returns `Schema::Union(Schema::Null, Schema::Bytes)`
-    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
+    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema {
         Schema::Union(
             UnionSchema::new(vec![Schema::Null, Schema::Bytes]).expect("This is a valid union"),
         )
@@ -179,7 +181,7 @@ pub mod bytes_opt {
     pub fn get_record_fields_in_ctxt(
         _: usize,
         _: &mut HashSet<Name>,
-        _: &Namespace,
+        _: NamespaceRef,
     ) -> Option<Vec<RecordField>> {
         None
     }
@@ -232,19 +234,22 @@ pub mod fixed {
     use super::BytesType;
     use serde::{Deserializer, Serializer};
 
+    use crate::schema::NamespaceRef;
     use crate::{
         Schema,
-        schema::{FixedSchema, Name, Namespace, RecordField},
+        schema::{FixedSchema, Name, RecordField},
     };
 
     /// Returns `Schema::Fixed(N)` named `serde_avro_fixed_{N}`
     pub fn get_schema_in_ctxt<const N: usize>(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
-        let name = Name::new(&format!("serde_avro_fixed_{N}"))
-            .expect("Name is valid")
-            .fully_qualified_name(enclosing_namespace);
+        let name = Name::new_with_enclosing_namespace(
+            format!("serde_avro_fixed_{N}"),
+            enclosing_namespace,
+        )
+        .expect("Name is valid");
         if named_schemas.contains(&name) {
             Schema::Ref { name }
         } else {
@@ -258,7 +263,7 @@ pub mod fixed {
     pub fn get_record_fields_in_ctxt(
         _: usize,
         _: &mut HashSet<Name>,
-        _: &Namespace,
+        _: NamespaceRef,
     ) -> Option<Vec<RecordField>> {
         None
     }
@@ -309,15 +314,16 @@ pub mod fixed_opt {
     use serde::{Deserializer, Serializer};
     use std::{borrow::Borrow, collections::HashSet};
 
+    use crate::schema::NamespaceRef;
     use crate::{
         Schema,
-        schema::{Name, Namespace, RecordField, UnionSchema},
+        schema::{Name, RecordField, UnionSchema},
     };
 
     /// Returns `Schema::Union(Schema::Null, Schema::Fixed(N))` where the fixed schema is named `serde_avro_fixed_{N}`
     pub fn get_schema_in_ctxt<const N: usize>(
         named_schemas: &mut HashSet<Name>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Schema {
         Schema::Union(
             UnionSchema::new(vec![
@@ -332,7 +338,7 @@ pub mod fixed_opt {
     pub fn get_record_fields_in_ctxt(
         _: usize,
         _: &mut HashSet<Name>,
-        _: &Namespace,
+        _: NamespaceRef,
     ) -> Option<Vec<RecordField>> {
         None
     }
@@ -387,13 +393,14 @@ pub mod slice {
 
     use serde::{Deserializer, Serializer};
 
+    use crate::schema::NamespaceRef;
     use crate::{
         Schema,
-        schema::{Name, Namespace, RecordField},
+        schema::{Name, RecordField},
     };
 
     /// Returns [`Schema::Bytes`]
-    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
+    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema {
         Schema::Bytes
     }
 
@@ -401,7 +408,7 @@ pub mod slice {
     pub fn get_record_fields_in_ctxt(
         _: usize,
         _: &mut HashSet<Name>,
-        _: &Namespace,
+        _: NamespaceRef,
     ) -> Option<Vec<RecordField>> {
         None
     }
@@ -455,13 +462,14 @@ pub mod slice_opt {
     use serde::{Deserializer, Serializer};
     use std::{borrow::Borrow, collections::HashSet};
 
+    use crate::schema::NamespaceRef;
     use crate::{
         Schema,
-        schema::{Name, Namespace, RecordField, UnionSchema},
+        schema::{Name, RecordField, UnionSchema},
     };
 
     /// Returns `Schema::Union(Schema::Null, Schema::Bytes)`
-    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: &Namespace) -> Schema {
+    pub fn get_schema_in_ctxt(_: &mut HashSet<Name>, _: NamespaceRef) -> Schema {
         Schema::Union(
             UnionSchema::new(vec![Schema::Null, Schema::Bytes]).expect("This is a valid union"),
         )
@@ -471,7 +479,7 @@ pub mod slice_opt {
     pub fn get_record_fields_in_ctxt(
         _: usize,
         _: &mut HashSet<Name>,
-        _: &Namespace,
+        _: NamespaceRef,
     ) -> Option<Vec<RecordField>> {
         None
     }
diff --git a/avro/src/types.rs b/avro/src/types.rs
index 0b23dc9..90ea2b0 100644
--- a/avro/src/types.rs
+++ b/avro/src/types.rs
@@ -16,7 +16,7 @@
 // under the License.
 
 //! Logic handling the intermediate representation of Avro values.
-use crate::schema::{InnerDecimalSchema, UuidSchema};
+use crate::schema::{InnerDecimalSchema, NamespaceRef, UuidSchema};
 use crate::{
     AvroResult, Error,
     bigdecimal::{deserialize_big_decimal, serialize_big_decimal},
@@ -24,8 +24,8 @@ use crate::{
     duration::Duration,
     error::Details,
     schema::{
-        DecimalSchema, EnumSchema, FixedSchema, Name, Namespace, Precision, RecordField,
-        RecordSchema, ResolvedSchema, Scale, Schema, SchemaKind, UnionSchema,
+        DecimalSchema, EnumSchema, FixedSchema, Name, Precision, RecordField, RecordSchema,
+        ResolvedSchema, Scale, Schema, SchemaKind, UnionSchema,
     },
 };
 use bigdecimal::BigDecimal;
@@ -383,7 +383,7 @@ impl Value {
         schemata.iter().any(|schema| {
             let enclosing_namespace = schema.namespace();
 
-            match self.validate_internal(schema, rs.get_names(), &enclosing_namespace) {
+            match self.validate_internal(schema, rs.get_names(), enclosing_namespace) {
                 Some(reason) => {
                     let log_message =
                         format!("Invalid value: {self:?} for schema: {schema:?}. Reason: {reason}");
@@ -409,11 +409,11 @@ impl Value {
     }
 
     /// Validates the value against the provided schema.
-    pub(crate) fn validate_internal<S: std::borrow::Borrow<Schema> + Debug>(
+    pub(crate) fn validate_internal<S: Borrow<Schema> + Debug>(
         &self,
         schema: &Schema,
         names: &HashMap<Name, S>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Option<String> {
         match (self, schema) {
             (_, Schema::Ref { name }) => {
@@ -426,7 +426,7 @@ impl Value {
                             names.keys()
                         ))
                     },
-                    |s| self.validate_internal(s.borrow(), names, &name.namespace),
+                    |s| self.validate_internal(s.borrow(), names, name.namespace()),
                 )
             }
             (&Value::Null, &Schema::Null) => None,
@@ -607,11 +607,7 @@ impl Value {
                 record_fields
                     .iter()
                     .fold(None, |acc, (field_name, record_field)| {
-                        let record_namespace = if name.namespace.is_none() {
-                            enclosing_namespace
-                        } else {
-                            &name.namespace
-                        };
+                        let record_namespace = name.namespace().or(enclosing_namespace);
                         match lookup.get(field_name) {
                             Some(idx) => {
                                 let field = &fields[*idx];
@@ -678,14 +674,14 @@ impl Value {
         } else {
             ResolvedSchema::try_from(schemata)?
         };
-        self.resolve_internal(schema, rs.get_names(), &enclosing_namespace, &None)
+        self.resolve_internal(schema, rs.get_names(), enclosing_namespace, &None)
     }
 
     pub(crate) fn resolve_internal<S: Borrow<Schema> + Debug>(
         mut self,
         schema: &Schema,
         names: &HashMap<Name, S>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
         field_default: &Option<JsonValue>,
     ) -> AvroResult<Self> {
         // Check if this schema is a union, and if the reader schema is not.
@@ -705,10 +701,10 @@ impl Value {
 
                 if let Some(resolved) = names.get(&name) {
                     debug!("Resolved {name:?}");
-                    self.resolve_internal(resolved.borrow(), names, &name.namespace, field_default)
+                    self.resolve_internal(resolved.borrow(), names, name.namespace(), field_default)
                 } else {
                     error!("Failed to resolve schema {name:?}");
-                    Err(Details::SchemaResolutionError(name.clone()).into())
+                    Err(Details::SchemaResolutionError(name.into_owned()).into())
                 }
             }
             Schema::Null => self.resolve_null(),
@@ -1082,7 +1078,7 @@ impl Value {
         self,
         schema: &UnionSchema,
         names: &HashMap<Name, S>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
         field_default: &Option<JsonValue>,
     ) -> Result<Self, Error> {
         let v = match self {
@@ -1108,7 +1104,7 @@ impl Value {
         self,
         schema: &Schema,
         names: &HashMap<Name, S>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Result<Self, Error> {
         match self {
             Value::Array(items) => Ok(Value::Array(
@@ -1129,7 +1125,7 @@ impl Value {
         self,
         schema: &Schema,
         names: &HashMap<Name, S>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Result<Self, Error> {
         match self {
             Value::Map(items) => Ok(Value::Map(
@@ -1154,7 +1150,7 @@ impl Value {
         self,
         fields: &[RecordField],
         names: &HashMap<Name, S>,
-        enclosing_namespace: &Namespace,
+        enclosing_namespace: NamespaceRef,
     ) -> Result<Self, Error> {
         let mut items = match self {
             Value::Map(items) => Ok(items),
@@ -1395,7 +1391,7 @@ mod tests {
                     attributes: BTreeMap::new(),
                 }),
                 false,
-                r#"Invalid value: Fixed(11, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) for schema: Duration(FixedSchema { name: Name { name: "TestName", namespace: None }, size: 12, .. }). Reason: The value's size ('11') must be exactly 12 to be a Duration"#,
+                r#"Invalid value: Fixed(11, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) for schema: Duration(FixedSchema { name: Name { name: "TestName", .. }, size: 12, .. }). Reason: The value's size ('11') must be exactly 12 to be a Duration"#,
             ),
             (
                 Value::Record(vec![("unknown_field_name".to_string(), Value::Null)]),
@@ -1413,7 +1409,7 @@ mod tests {
                     attributes: Default::default(),
                 }),
                 false,
-                r#"Invalid value: Record([("unknown_field_name", Null)]) for schema: Record(RecordSchema { name: Name { name: "record_name", namespace: None }, fields: [RecordField { name: "field_name", schema: Int, .. }], .. }). Reason: There is no schema field for field 'unknown_field_name'"#,
+                r#"Invalid value: Record([("unknown_field_name", Null)]) for schema: Record(RecordSchema { name: Name { name: "record_name", .. }, fields: [RecordField { name: "field_name", schema: Int, .. }], .. }). Reason: There is no schema field for field 'unknown_field_name'"#,
             ),
             (
                 Value::Record(vec![("field_name".to_string(), Value::Null)]),
@@ -1433,13 +1429,12 @@ mod tests {
                     attributes: Default::default(),
                 }),
                 false,
-                r#"Invalid value: Record([("field_name", Null)]) for schema: Record(RecordSchema { name: Name { name: "record_name", namespace: None }, fields: [RecordField { name: "field_name", schema: Ref { name: Name { name: "missing", namespace: None } }, .. }], .. }). Reason: Unresolved schema reference: 'Name { name: "missing", namespace: None }'. Parsed names: []"#,
+                r#"Invalid value: Record([("field_name", Null)]) for schema: Record(RecordSchema { name: Name { name: "record_name", .. }, fields: [RecordField { name: "field_name", schema: Ref { name: Name { name: "missing", .. } }, .. }], .. }). Reason: Unresolved schema reference: 'Name { name: "missing", .. }'. Parsed names: []"#,
             ),
         ];
 
         for (value, schema, valid, expected_err_message) in value_schema_valid.into_iter() {
-            let err_message =
-                value.validate_internal::<Schema>(&schema, &HashMap::default(), &None);
+            let err_message = value.validate_internal::<Schema>(&schema, &HashMap::default(), None);
             assert_eq!(valid, err_message.is_none());
             if !valid {
                 let full_err_message = format!(
@@ -1633,7 +1628,7 @@ mod tests {
         ]);
         assert!(!value.validate(&schema));
         assert_logged(
-            r#"Invalid value: Record([("a", Boolean(false)), ("b", String("foo"))]) for schema: Record(RecordSchema { name: Name { name: "some_record", namespace: None }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: Unsupported value-schema combination! Value: Boolean(false), schema: Long"#,
+            r#"Invalid value: Record([("a", Boolean(false)), ("b", String("foo"))]) for schema: Record(RecordSchema { name: Name { name: "some_record", .. }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: Unsupported value-schema combination! Value: Boolean(false), schema: Long"#,
         );
 
         let value = Value::Record(vec![
@@ -1642,7 +1637,7 @@ mod tests {
         ]);
         assert!(!value.validate(&schema));
         assert_logged(
-            r#"Invalid value: Record([("a", Long(42)), ("c", String("foo"))]) for schema: Record(RecordSchema { name: Name { name: "some_record", namespace: None }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: Could not find matching type in union"#,
+            r#"Invalid value: Record([("a", Long(42)), ("c", String("foo"))]) for schema: Record(RecordSchema { name: Name { name: "some_record", .. }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: Could not find matching type in union"#,
         );
         assert_not_logged(
             r#"Invalid value: String("foo") for schema: Int. Reason: Unsupported value-schema combination"#,
@@ -1654,7 +1649,7 @@ mod tests {
         ]);
         assert!(!value.validate(&schema));
         assert_logged(
-            r#"Invalid value: Record([("a", Long(42)), ("d", String("foo"))]) for schema: Record(RecordSchema { name: Name { name: "some_record", namespace: None }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: There is no schema field for field 'd'"#,
+            r#"Invalid value: Record([("a", Long(42)), ("d", String("foo"))]) for schema: Record(RecordSchema { name: Name { name: "some_record", .. }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: There is no schema field for field 'd'"#,
         );
 
         let value = Value::Record(vec![
@@ -1665,7 +1660,7 @@ mod tests {
         ]);
         assert!(!value.validate(&schema));
         assert_logged(
-            r#"Invalid value: Record([("a", Long(42)), ("b", String("foo")), ("c", Null), ("d", Null)]) for schema: Record(RecordSchema { name: Name { name: "some_record", namespace: None }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: The value's records length (4) is greater than the schema's (3 fields)"#,
+            r#"Invalid value: Record([("a", Long(42)), ("b", String("foo")), ("c", Null), ("d", Null)]) for schema: Record(RecordSchema { name: Name { name: "some_record", .. }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: The value's records length (4) is greater than the schema's (3 fields)"#,
         );
 
         assert!(
@@ -1689,7 +1684,7 @@ mod tests {
             .validate(&schema)
         );
         assert_logged(
-            r#"Invalid value: Map({"d": Long(123)}) for schema: Record(RecordSchema { name: Name { name: "some_record", namespace: None }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: Field with name '"a"' is not a member of the map items
+            r#"Invalid value: Map({"d": Long(123)}) for schema: Record(RecordSchema { name: Name { name: "some_record", .. }, fields: [RecordField { name: "a", schema: Long, .. }, RecordField { name: "b", schema: String, .. }, RecordField { name: "c", default: Null, schema: Union(UnionSchema { schemas: [Null, Int] }), .. }], .. }). Reason: Field with name '"a"' is not a member of the map items
 Field with name '"b"' is not a member of the map items"#,
         );
 
@@ -1959,10 +1954,7 @@ Field with name '"b"' is not a member of the map items"#,
             value
                 .clone()
                 .resolve(&Schema::Uuid(UuidSchema::Fixed(FixedSchema {
-                    name: Name {
-                        name: "some_name".to_string(),
-                        namespace: None
-                    },
+                    name: Name::new("some_name")?,
                     aliases: None,
                     doc: None,
                     size: 16,
diff --git a/avro/src/util.rs b/avro/src/util.rs
index 3127f8d..8728c9e 100644
--- a/avro/src/util.rs
+++ b/avro/src/util.rs
@@ -41,24 +41,22 @@ pub const DEFAULT_SERDE_HUMAN_READABLE: bool = false;
 pub(crate) static SERDE_HUMAN_READABLE: OnceLock<bool> = OnceLock::new();
 
 pub(crate) trait MapHelper {
-    fn string(&self, key: &str) -> Option<String>;
+    fn string(&self, key: &str) -> Option<&str>;
 
-    fn name(&self) -> Option<String> {
+    fn name(&self) -> Option<&str> {
         self.string("name")
     }
 
     fn doc(&self) -> Documentation {
-        self.string("doc")
+        self.string("doc").map(Into::into)
     }
 
     fn aliases(&self) -> Option<Vec<String>>;
 }
 
 impl MapHelper for Map<String, Value> {
-    fn string(&self, key: &str) -> Option<String> {
-        self.get(key)
-            .and_then(|v| v.as_str())
-            .map(|v| v.to_string())
+    fn string(&self, key: &str) -> Option<&str> {
+        self.get(key).and_then(|v| v.as_str())
     }
 
     fn aliases(&self) -> Option<Vec<String>> {
diff --git a/avro/src/validator.rs b/avro/src/validator.rs
index 0162bce..9165b3e 100644
--- a/avro/src/validator.rs
+++ b/avro/src/validator.rs
@@ -17,27 +17,33 @@
 
 //! # Custom name validation
 //!
-//! By default, the library follows the rules specified in the [Avro specification](https://avro.apache.org/docs/1.11.1/specification/#names).
+//! By default, the library follows the rules specified in the [Avro specification](https://avro.apache.org/docs/++version++/specification/#names).
 //!
 //! Some of the other Apache Avro language SDKs are more flexible in their name validation. For
 //! interoperability with those SDKs, the library provides a way to customize the name validation.
 //!
 //! ```
-//! use apache_avro::AvroResult;
-//! use apache_avro::schema::Namespace;
-//! use apache_avro::validator::{SchemaNameValidator, set_schema_name_validator};
+//! # use apache_avro::{AvroResult, validator::{SchemaNameValidator, set_schema_name_validator}};
+//! # use regex_lite::Regex;
+//! # use std::sync::OnceLock;
+//! struct DontAllowNamespaces;
 //!
-//! struct MyCustomValidator;
-//!
-//! impl SchemaNameValidator for MyCustomValidator {
-//!     fn validate(&self, name: &str) -> AvroResult<(String, Namespace)> {
-//!         todo!()
+//! impl SchemaNameValidator for DontAllowNamespaces {
+//!     fn regex(&self) -> &'static Regex {
+//!         static SCHEMA_NAME_ONCE: OnceLock<Regex> = OnceLock::new();
+//!         SCHEMA_NAME_ONCE.get_or_init(|| {
+//!             Regex::new(
+//!                 // Disallows any namespace. By naming the group `name`, the default
+//!                 // implementation of `SchemaNameValidator::validate` can be reused.
+//!                 r"^(?P<name>[A-Za-z_][A-Za-z0-9_]*)$",
+//!             ).expect("Regex is valid")
+//!         })
 //!     }
 //! }
 //!
 //! // don't parse any schema before registering the custom validator(s)!
 //!
-//! if set_schema_name_validator(Box::new(MyCustomValidator)).is_err() {
+//! if set_schema_name_validator(Box::new(DontAllowNamespaces)).is_err() {
 //!     // `.unwrap()` doesn't work as the return type does not implement `Debug`
 //!     panic!("There was already a schema validator configured")
 //! }
@@ -51,7 +57,7 @@
 //! If the application parses schemas before setting a validator, the default validator will be
 //! registered and used!
 
-use crate::{AvroResult, error::Details, schema::Namespace};
+use crate::{AvroResult, error::Details};
 use log::debug;
 use regex_lite::Regex;
 use std::sync::OnceLock;
@@ -65,6 +71,9 @@ struct SpecificationValidator;
 pub trait SchemaNameValidator: Send + Sync {
     /// The regex used to validate the schema name.
     ///
+    /// When the name part of the full name is provided as a capture group named `name`, the
+    /// default implementation of [`Self::validate`] can be used.
+    ///
     /// The default implementation uses the Avro specified regex.
     fn regex(&self) -> &'static Regex {
         static SCHEMA_NAME_ONCE: OnceLock<Regex> = OnceLock::new();
@@ -77,25 +86,26 @@ pub trait SchemaNameValidator: Send + Sync {
         })
     }
 
-    /// Validates the schema name and returns the name and the optional namespace.
+    /// Validates the schema name and returns the start byte of the name.
+    ///
+    /// Requires that the implementation of [`Self::regex`] provides a capture group named `name`
+    /// that captures the name part of the full name.
     ///
     /// Should return [`Details::InvalidSchemaName`] if it is invalid.
-    fn validate(&self, schema_name: &str) -> AvroResult<(String, Namespace)>;
-}
-
-impl SchemaNameValidator for SpecificationValidator {
-    fn validate(&self, schema_name: &str) -> AvroResult<(String, Namespace)> {
+    fn validate(&self, schema_name: &str) -> AvroResult<usize> {
         let regex = SchemaNameValidator::regex(self);
         let caps = regex
             .captures(schema_name)
             .ok_or_else(|| Details::InvalidSchemaName(schema_name.to_string(), regex.as_str()))?;
-        Ok((
-            caps["name"].to_string(),
-            caps.name("namespace").map(|s| s.as_str().to_string()),
-        ))
+        Ok(caps
+            .name("name")
+            .expect("Regex has no group named `name`")
+            .start())
     }
 }
 
+impl SchemaNameValidator for SpecificationValidator {}
+
 static NAME_VALIDATOR_ONCE: OnceLock<Box<dyn SchemaNameValidator + Send + Sync>> = OnceLock::new();
 
 /// Sets a custom schema name validator.
@@ -111,7 +121,7 @@ pub fn set_schema_name_validator(
     NAME_VALIDATOR_ONCE.set(validator)
 }
 
-pub(crate) fn validate_schema_name(schema_name: &str) -> AvroResult<(String, Namespace)> {
+pub(crate) fn validate_schema_name(schema_name: &str) -> AvroResult<usize> {
     NAME_VALIDATOR_ONCE
         .get_or_init(|| {
             debug!("Going to use the default name validator.");
@@ -137,20 +147,18 @@ pub trait SchemaNamespaceValidator: Send + Sync {
     /// Validates a schema namespace.
     ///
     /// Should return [`Details::InvalidNamespace`] if it is invalid.
-    fn validate(&self, namespace: &str) -> AvroResult<()>;
-}
-
-impl SchemaNamespaceValidator for SpecificationValidator {
-    fn validate(&self, ns: &str) -> AvroResult<()> {
+    fn validate(&self, namespace: &str) -> AvroResult<()> {
         let regex = SchemaNamespaceValidator::regex(self);
-        if !regex.is_match(ns) {
-            Err(Details::InvalidNamespace(ns.to_string(), regex.as_str()).into())
+        if !regex.is_match(namespace) {
+            Err(Details::InvalidNamespace(namespace.to_string(), regex.as_str()).into())
         } else {
             Ok(())
         }
     }
 }
 
+impl SchemaNamespaceValidator for SpecificationValidator {}
+
 static NAMESPACE_VALIDATOR_ONCE: OnceLock<Box<dyn SchemaNamespaceValidator + Send + Sync>> =
     OnceLock::new();
 
@@ -190,10 +198,6 @@ pub trait EnumSymbolNameValidator: Send + Sync {
     /// Validate the symbol of an enum.
     ///
     /// Should return [`Details::EnumSymbolName`] if it is invalid.
-    fn validate(&self, name: &str) -> AvroResult<()>;
-}
-
-impl EnumSymbolNameValidator for SpecificationValidator {
     fn validate(&self, symbol: &str) -> AvroResult<()> {
         let regex = EnumSymbolNameValidator::regex(self);
         if !regex.is_match(symbol) {
@@ -204,6 +208,8 @@ impl EnumSymbolNameValidator for SpecificationValidator {
     }
 }
 
+impl EnumSymbolNameValidator for SpecificationValidator {}
+
 static ENUM_SYMBOL_NAME_VALIDATOR_ONCE: OnceLock<Box<dyn EnumSymbolNameValidator + Send + Sync>> =
     OnceLock::new();
 
@@ -243,10 +249,6 @@ pub trait RecordFieldNameValidator: Send + Sync {
     /// Validate the name of a record field.
     ///
     /// Should return [`Details::FieldName`] if it is invalid.
-    fn validate(&self, name: &str) -> AvroResult<()>;
-}
-
-impl RecordFieldNameValidator for SpecificationValidator {
     fn validate(&self, field_name: &str) -> AvroResult<()> {
         let regex = RecordFieldNameValidator::regex(self);
         if !regex.is_match(field_name) {
@@ -257,6 +259,8 @@ impl RecordFieldNameValidator for SpecificationValidator {
     }
 }
 
+impl RecordFieldNameValidator for SpecificationValidator {}
+
 static RECORD_FIELD_NAME_VALIDATOR_ONCE: OnceLock<Box<dyn RecordFieldNameValidator + Send + Sync>> =
     OnceLock::new();
 
diff --git a/avro/src/writer.rs b/avro/src/writer.rs
index 3c9bf86..fe264a1 100644
--- a/avro/src/writer.rs
+++ b/avro/src/writer.rs
@@ -201,7 +201,7 @@ impl<'a, W: Write> Writer<'a, W> {
         if let Some(reason) = value.validate_internal(
             self.schema,
             self.resolved_schema.get_names(),
-            &self.schema.namespace(),
+            self.schema.namespace(),
         ) {
             return Err(Details::ValidationWithReason {
                 value: value.clone(),
@@ -246,7 +246,7 @@ impl<'a, W: Write> Writer<'a, W> {
             value,
             self.schema,
             self.resolved_schema.get_names(),
-            &self.schema.namespace(),
+            self.schema.namespace(),
             &mut self.buffer,
         )?;
 
@@ -641,10 +641,10 @@ fn write_avro_datum_schemata<T: Into<Value>>(
     let rs = ResolvedSchema::try_from(schemata)?;
     let names = rs.get_names();
     let enclosing_namespace = schema.namespace();
-    if let Some(_err) = avro.validate_internal(schema, names, &enclosing_namespace) {
+    if let Some(_err) = avro.validate_internal(schema, names, enclosing_namespace) {
         return Err(Details::Validation.into());
     }
-    encode_internal(&avro, schema, names, &enclosing_namespace, buffer)
+    encode_internal(&avro, schema, names, enclosing_namespace, buffer)
 }
 
 /// Writer that encodes messages according to the single object encoding v1 spec
@@ -811,7 +811,7 @@ fn write_value_ref_owned_resolved<W: Write>(
     if let Some(reason) = value.validate_internal(
         root_schema,
         resolved_schema.get_names(),
-        &root_schema.namespace(),
+        root_schema.namespace(),
     ) {
         return Err(Details::ValidationWithReason {
             value: value.clone(),
@@ -824,7 +824,7 @@ fn write_value_ref_owned_resolved<W: Write>(
         value,
         root_schema,
         resolved_schema.get_names(),
-        &root_schema.namespace(),
+        root_schema.namespace(),
         writer,
     )
 }
@@ -1785,7 +1785,7 @@ mod tests {
             Err(e) => {
                 assert_eq!(
                     e.to_string(),
-                    r#"Failed to serialize field 'time' for record Record(RecordSchema { name: Name { name: "Conference", namespace: None }, fields: [RecordField { name: "name", schema: String, .. }, RecordField { name: "date", aliases: ["time2", "time"], schema: Union(UnionSchema { schemas: [Null, Long] }), .. }], .. }): Failed to serialize value of type f64 using schema Union(UnionSchema { schemas: [Null, Long] }): 12345678.9. Cause: Cannot find a Double schema in [Null, Long]"#
+                    r#"Failed to serialize field 'time' for record Record(RecordSchema { name: Name { name: "Conference", .. }, fields: [RecordField { name: "name", schema: String, .. }, RecordField { name: "date", aliases: ["time2", "time"], schema: Union(UnionSchema { schemas: [Null, Long] }), .. }], .. }): Failed to serialize value of type f64 using schema Union(UnionSchema { schemas: [Null, Long] }): 12345678.9. Cause: Cannot find a Double schema in [Null, Long]"#
                 );
             }
         }
diff --git a/avro/tests/avro_schema_component.rs b/avro/tests/avro_schema_component.rs
index 596f35f..ca9e31a 100644
--- a/avro/tests/avro_schema_component.rs
+++ b/avro/tests/avro_schema_component.rs
@@ -20,7 +20,7 @@ use std::collections::HashSet;
 
 #[test]
 fn avro_rs_394_avro_schema_component_without_derive_feature() {
-    let schema = i32::get_schema_in_ctxt(&mut HashSet::default(), &None);
+    let schema = i32::get_schema_in_ctxt(&mut HashSet::default(), None);
     assert!(matches!(schema, Schema::Int));
 }
 
@@ -29,5 +29,5 @@ fn avro_rs_394_avro_schema_component_without_derive_feature() {
 fn avro_rs_394_avro_schema_component_nested_options() {
     type VeryOptional = Option<Option<i32>>;
 
-    let _schema = VeryOptional::get_schema_in_ctxt(&mut HashSet::default(), &None);
+    let _schema = VeryOptional::get_schema_in_ctxt(&mut HashSet::default(), None);
 }
diff --git a/avro/tests/get_record_fields.rs b/avro/tests/get_record_fields.rs
index 765fda6..5bab721 100644
--- a/avro/tests/get_record_fields.rs
+++ b/avro/tests/get_record_fields.rs
@@ -33,7 +33,7 @@ fn avro_rs_448_default_get_record_fields_no_recursion() -> TestResult {
 
     let mut named_schemas = HashSet::new();
     let fields =
-        get_record_fields_in_ctxt(&mut named_schemas, &None, Foo::get_schema_in_ctxt).unwrap();
+        get_record_fields_in_ctxt(&mut named_schemas, None, Foo::get_schema_in_ctxt).unwrap();
 
     assert_eq!(fields.len(), 2);
     assert!(
@@ -42,7 +42,7 @@ fn avro_rs_448_default_get_record_fields_no_recursion() -> TestResult {
     );
 
     // Insert Foo into named_schemas
-    match Foo::get_schema_in_ctxt(&mut named_schemas, &None) {
+    match Foo::get_schema_in_ctxt(&mut named_schemas, None) {
         Schema::Record(_) => {}
         schema => panic!("Expected a record got {schema:?}"),
     }
@@ -53,7 +53,7 @@ fn avro_rs_448_default_get_record_fields_no_recursion() -> TestResult {
     );
 
     let fields =
-        get_record_fields_in_ctxt(&mut named_schemas, &None, Foo::get_schema_in_ctxt).unwrap();
+        get_record_fields_in_ctxt(&mut named_schemas, None, Foo::get_schema_in_ctxt).unwrap();
     assert_eq!(fields.len(), 2);
     assert_eq!(
         named_schemas.len(),
@@ -74,7 +74,7 @@ fn avro_rs_448_default_get_record_fields_recursion() -> TestResult {
 
     let mut named_schemas = HashSet::new();
     let fields =
-        get_record_fields_in_ctxt(&mut named_schemas, &None, Foo::get_schema_in_ctxt).unwrap();
+        get_record_fields_in_ctxt(&mut named_schemas, None, Foo::get_schema_in_ctxt).unwrap();
 
     assert_eq!(fields.len(), 2);
     assert_eq!(
@@ -84,14 +84,14 @@ fn avro_rs_448_default_get_record_fields_recursion() -> TestResult {
     );
 
     // Insert Foo into named_schemas
-    match Foo::get_schema_in_ctxt(&mut named_schemas, &None) {
+    match Foo::get_schema_in_ctxt(&mut named_schemas, None) {
         Schema::Ref { name: _ } => {}
         schema => panic!("Expected a ref got {schema:?}"),
     }
     assert_eq!(named_schemas.len(), 1);
 
     let fields =
-        get_record_fields_in_ctxt(&mut named_schemas, &None, Foo::get_schema_in_ctxt).unwrap();
+        get_record_fields_in_ctxt(&mut named_schemas, None, Foo::get_schema_in_ctxt).unwrap();
     assert_eq!(fields.len(), 2);
     assert_eq!(
         named_schemas.len(),
diff --git a/avro/tests/schema.rs b/avro/tests/schema.rs
index e7fb61f..3347e83 100644
--- a/avro/tests/schema.rs
+++ b/avro/tests/schema.rs
@@ -73,7 +73,7 @@ fn test_correct_recursive_extraction() -> TestResult {
                 ..
             }) = &inner_fields[0].schema
             {
-                assert_eq!("X", recursive_type.name.as_str());
+                assert_eq!("X", recursive_type.name());
             }
         } else {
             panic!("inner schema {inner_schema:?} should have been a record")
@@ -598,8 +598,8 @@ fn test_fullname_name_and_namespace_specified() -> TestResult {
 fn test_fullname_fullname_and_namespace_specified() -> TestResult {
     init();
     let name: Name = serde_json::from_str(r#"{"name": "a.b.c.d", "namespace": "o.a.h"}"#)?;
-    assert_eq!(&name.name, "d");
-    assert_eq!(name.namespace, Some("a.b.c".to_owned()));
+    assert_eq!(name.name(), "d");
+    assert_eq!(name.namespace(), Some("a.b.c"));
     let fullname = name.fullname(None);
     assert_eq!("a.b.c.d", fullname);
     Ok(())
@@ -609,9 +609,9 @@ fn test_fullname_fullname_and_namespace_specified() -> TestResult {
 fn test_fullname_name_and_default_namespace_specified() -> TestResult {
     init();
     let name: Name = serde_json::from_str(r#"{"name": "a", "namespace": null}"#)?;
-    assert_eq!(&name.name, "a");
-    assert_eq!(name.namespace, None);
-    let fullname = name.fullname(Some("b.c.d".into()));
+    assert_eq!(name.name(), "a");
+    assert_eq!(name.namespace(), None);
+    let fullname = name.fullname(Some("b.c.d"));
     assert_eq!("b.c.d.a", fullname);
     Ok(())
 }
@@ -620,9 +620,9 @@ fn test_fullname_name_and_default_namespace_specified() -> TestResult {
 fn test_fullname_fullname_and_default_namespace_specified() -> TestResult {
     init();
     let name: Name = serde_json::from_str(r#"{"name": "a.b.c.d", "namespace": null}"#)?;
-    assert_eq!(&name.name, "d");
-    assert_eq!(name.namespace, Some("a.b.c".to_owned()));
-    let fullname = name.fullname(Some("o.a.h".into()));
+    assert_eq!(name.name(), "d");
+    assert_eq!(name.namespace(), Some("a.b.c"));
+    let fullname = name.fullname(Some("o.a.h"));
     assert_eq!("a.b.c.d", fullname);
     Ok(())
 }
@@ -631,8 +631,8 @@ fn test_fullname_fullname_and_default_namespace_specified() -> TestResult {
 fn test_avro_3452_parsing_name_without_namespace() -> TestResult {
     init();
     let name: Name = serde_json::from_str(r#"{"name": "a.b.c.d"}"#)?;
-    assert_eq!(&name.name, "d");
-    assert_eq!(name.namespace, Some("a.b.c".to_owned()));
+    assert_eq!(name.name(), "d");
+    assert_eq!(name.namespace(), Some("a.b.c"));
     let fullname = name.fullname(None);
     assert_eq!("a.b.c.d", fullname);
     Ok(())
@@ -642,8 +642,8 @@ fn test_avro_3452_parsing_name_without_namespace() -> TestResult {
 fn test_avro_3452_parsing_name_with_leading_dot_without_namespace() -> TestResult {
     init();
     let name: Name = serde_json::from_str(r#"{"name": ".a"}"#)?;
-    assert_eq!(&name.name, "a");
-    assert_eq!(name.namespace, None);
+    assert_eq!(name.name(), "a");
+    assert_eq!(name.namespace(), None);
     assert_eq!("a", name.fullname(None));
     Ok(())
 }
@@ -662,9 +662,9 @@ fn test_fullname_fullname_namespace_and_default_namespace_specified() -> TestRes
     init();
     let name: Name =
         serde_json::from_str(r#"{"name": "a.b.c.d", "namespace": "o.a.a", "aliases": null}"#)?;
-    assert_eq!(&name.name, "d");
-    assert_eq!(name.namespace, Some("a.b.c".to_owned()));
-    let fullname = name.fullname(Some("o.a.h".into()));
+    assert_eq!(name.name(), "d");
+    assert_eq!(name.namespace(), Some("a.b.c"));
+    let fullname = name.fullname(Some("o.a.h"));
     assert_eq!("a.b.c.d", fullname);
     Ok(())
 }
@@ -674,9 +674,9 @@ fn test_fullname_name_namespace_and_default_namespace_specified() -> TestResult
     init();
     let name: Name =
         serde_json::from_str(r#"{"name": "a", "namespace": "o.a.a", "aliases": null}"#)?;
-    assert_eq!(&name.name, "a");
-    assert_eq!(name.namespace, Some("o.a.a".to_owned()));
-    let fullname = name.fullname(Some("o.a.h".into()));
+    assert_eq!(name.name(), "a");
+    assert_eq!(name.namespace(), Some("o.a.a"));
+    let fullname = name.fullname(Some("o.a.h"));
     assert_eq!("o.a.a.a", fullname);
     Ok(())
 }
diff --git a/avro/tests/serde_human_readable_true.rs b/avro/tests/serde_human_readable_true.rs
index c71a91e..d1c71d7 100644
--- a/avro/tests/serde_human_readable_true.rs
+++ b/avro/tests/serde_human_readable_true.rs
@@ -129,7 +129,7 @@ fn avro_rs_440_uuid_fixed() -> TestResult {
     let writer = SpecificSingleObjectWriter::new()?;
     assert_eq!(
         writer.write(uuid, &mut buffer).unwrap_err().to_string(),
-        r#"Failed to serialize value of type string using schema Uuid(Fixed(FixedSchema { name: Name { name: "uuid", namespace: None }, size: 16, .. })): 550e8400-e29b-41d4-a716-446655440000. Cause: Expected bytes but got a string. Did you mean to use `Schema::Uuid(UuidSchema::String)` or `utils::serde_set_human_readable(false)`?"#
+        r#"Failed to serialize value of type string using schema Uuid(Fixed(FixedSchema { name: Name { name: "uuid", .. }, size: 16, .. })): 550e8400-e29b-41d4-a716-446655440000. Cause: Expected bytes but got a string. Did you mean to use `Schema::Uuid(UuidSchema::String)` or `utils::serde_set_human_readable(false)`?"#
     );
 
     Ok(())
diff --git a/avro/tests/validators.rs b/avro/tests/validators.rs
index 55659be..3325c1f 100644
--- a/avro/tests/validators.rs
+++ b/avro/tests/validators.rs
@@ -17,7 +17,6 @@
 
 use apache_avro::{
     AvroResult,
-    schema::Namespace,
     validator::{
         EnumSymbolNameValidator, RecordFieldNameValidator, SchemaNameValidator,
         SchemaNamespaceValidator, set_enum_symbol_name_validator, set_record_field_name_validator,
@@ -32,8 +31,8 @@ struct CustomValidator;
 // because the parsing will trigger the validation and will
 // setup the default validator (SpecificationValidator)!
 impl SchemaNameValidator for CustomValidator {
-    fn validate(&self, schema_name: &str) -> AvroResult<(String, Namespace)> {
-        Ok((schema_name.to_string(), None))
+    fn validate(&self, _schema_name: &str) -> AvroResult<usize> {
+        Ok(0)
     }
 }
 
diff --git a/avro_derive/src/lib.rs b/avro_derive/src/lib.rs
index 51d0117..52de29b 100644
--- a/avro_derive/src/lib.rs
+++ b/avro_derive/src/lib.rs
@@ -118,11 +118,11 @@ fn create_trait_definition(
     quote! {
         #[automatically_derived]
         impl #impl_generics ::apache_avro::AvroSchemaComponent for #ident #ty_generics #where_clause {
-            fn get_schema_in_ctxt(named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace: &::std::option::Option<::std::string::String>) -> ::apache_avro::schema::Schema {
+            fn get_schema_in_ctxt(named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace: ::apache_avro::schema::NamespaceRef) -> ::apache_avro::schema::Schema {
                 #get_schema_impl
             }
 
-            fn get_record_fields_in_ctxt(named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace: &::std::option::Option<::std::string::String>) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
+            fn get_record_fields_in_ctxt(named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>, enclosing_namespace: ::apache_avro::schema::NamespaceRef) -> ::std::option::Option<::std::vec::Vec<::apache_avro::schema::RecordField>> {
                 #get_record_fields_impl
             }
 
@@ -136,11 +136,11 @@ fn create_trait_definition(
 /// Generate the code to check `named_schemas` if this schema already exist
 fn handle_named_schemas(full_schema_name: String, schema_def: TokenStream) -> TokenStream {
     quote! {
-        let name = ::apache_avro::schema::Name::new(#full_schema_name).expect(concat!("Unable to parse schema name ", #full_schema_name)).fully_qualified_name(enclosing_namespace);
+        let name = ::apache_avro::schema::Name::new_with_enclosing_namespace(#full_schema_name, enclosing_namespace).expect(concat!("Unable to parse schema name ", #full_schema_name));
         if named_schemas.contains(&name) {
             ::apache_avro::schema::Schema::Ref{name}
         } else {
-            let enclosing_namespace = &name.namespace;
+            let enclosing_namespace = name.namespace();
             named_schemas.insert(name.clone());
             #schema_def
         }
@@ -597,15 +597,14 @@ mod tests {
                     impl ::apache_avro::AvroSchemaComponent for Basic {
                         fn get_schema_in_ctxt(
                             named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
-                            enclosing_namespace: &::std::option::Option<::std::string::String>
+                            enclosing_namespace: ::apache_avro::schema::NamespaceRef
                         ) -> ::apache_avro::schema::Schema {
-                            let name = ::apache_avro::schema::Name::new("Basic")
-                                .expect(concat!("Unable to parse schema name ", "Basic"))
-                                .fully_qualified_name(enclosing_namespace);
+                            let name = ::apache_avro::schema::Name::new_with_enclosing_namespace("Basic", enclosing_namespace)
+                                .expect(concat!("Unable to parse schema name ", "Basic"));
                             if named_schemas.contains(&name) {
                                 ::apache_avro::schema::Schema::Ref { name }
                             } else {
-                                let enclosing_namespace = &name.namespace;
+                                let enclosing_namespace = name.namespace();
                                 named_schemas.insert(name.clone());
                                 ::apache_avro::schema::Schema::Enum(apache_avro::schema::EnumSchema {
                                     name: ::apache_avro::schema::Name::new("Basic").expect(
@@ -627,7 +626,7 @@ mod tests {
 
                         fn get_record_fields_in_ctxt(
                             named_schemas: &mut ::std::collections::HashSet<::apache_avro::schema::Name>,
-                            enclosing_namespace: &::std::option::Option<::std::string::String>
+                            enclosing_namespace: ::apache_avro::schema::NamespaceRef
                         ) -> ::std::option::Option <::std::vec::Vec<::apache_avro::schema::RecordField>> {
                             None
                         }
@@ -761,7 +760,7 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_struct) {
             Ok(input) => {
                 let schema_res = derive_avro_schema(input);
-                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std :: string :: String >) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . fully_qual [...]
+                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : :: apache_avro :: schema :: NamespaceRef) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new_with_enclosing_namespace ("A" , enclosing_namespace) . expect (concat ! ("Unable to parse schema [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
                 assert_eq!(schema_token_stream, expected_token_stream);
             }
@@ -780,7 +779,7 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_enum) {
             Ok(input) => {
                 let schema_res = derive_avro_schema(input);
-                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std :: string :: String >) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . fully_qual [...]
+                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : :: apache_avro :: schema :: NamespaceRef) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new_with_enclosing_namespace ("A" , enclosing_namespace) . expect (concat ! ("Unable to parse schema [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
                 assert_eq!(schema_token_stream, expected_token_stream);
             }
@@ -803,7 +802,7 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_struct) {
             Ok(input) => {
                 let schema_res = derive_avro_schema(input);
-                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std :: string :: String >) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . fully_qual [...]
+                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : :: apache_avro :: schema :: NamespaceRef) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new_with_enclosing_namespace ("A" , enclosing_namespace) . expect (concat ! ("Unable to parse schema [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
                 assert_eq!(schema_token_stream, expected_token_stream);
             }
@@ -823,7 +822,7 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_enum) {
             Ok(input) => {
                 let schema_res = derive_avro_schema(input);
-                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for B { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std :: string :: String >) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new ("B") . expect (concat ! ("Unable to parse schema name " , "B")) . fully_qual [...]
+                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for B { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : :: apache_avro :: schema :: NamespaceRef) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new_with_enclosing_namespace ("B" , enclosing_namespace) . expect (concat ! ("Unable to parse schema [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
                 assert_eq!(schema_token_stream, expected_token_stream);
             }
@@ -847,7 +846,7 @@ mod tests {
         match syn::parse2::<DeriveInput>(test_struct) {
             Ok(input) => {
                 let schema_res = derive_avro_schema(input);
-                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : & :: std :: option :: Option < :: std :: string :: String >) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new ("A") . expect (concat ! ("Unable to parse schema name " , "A")) . fully_qual [...]
+                let expected_token_stream = r#"# [automatically_derived] impl :: apache_avro :: AvroSchemaComponent for A { fn get_schema_in_ctxt (named_schemas : & mut :: std :: collections :: HashSet < :: apache_avro :: schema :: Name > , enclosing_namespace : :: apache_avro :: schema :: NamespaceRef) -> :: apache_avro :: schema :: Schema { let name = :: apache_avro :: schema :: Name :: new_with_enclosing_namespace ("A" , enclosing_namespace) . expect (concat ! ("Unable to parse schema [...]
                 let schema_token_stream = schema_res.unwrap().to_string();
                 assert_eq!(schema_token_stream, expected_token_stream);
             }
diff --git a/avro_derive/tests/derive.rs b/avro_derive/tests/derive.rs
index b6855b8..ae366e3 100644
--- a/avro_derive/tests/derive.rs
+++ b/avro_derive/tests/derive.rs
@@ -17,7 +17,7 @@
 
 use apache_avro::{
     AvroSchema, AvroSchemaComponent, Reader, Schema, Writer, from_value,
-    schema::{Alias, EnumSchema, FixedSchema, Name, Namespace, RecordSchema},
+    schema::{Alias, EnumSchema, FixedSchema, Name, RecordSchema},
 };
 use proptest::prelude::*;
 use serde::{Deserialize, Serialize, de::DeserializeOwned};
@@ -29,6 +29,7 @@ use std::{
 };
 use uuid::Uuid;
 
+use apache_avro::schema::NamespaceRef;
 use pretty_assertions::assert_eq;
 
 /// Takes in a type that implements the right combination of traits and runs it through a Serde Cycle and asserts the result is the same
@@ -141,7 +142,7 @@ fn test_basic_namespace() {
     let schema = Schema::parse_str(schema).unwrap();
     assert_eq!(schema, TestBasicNamespace::get_schema());
     if let Schema::Record(RecordSchema { name, .. }) = TestBasicNamespace::get_schema() {
-        assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap())
+        assert_eq!(Some("com.testing.namespace"), name.namespace())
     } else {
         panic!("TestBasicNamespace schema must be a record schema")
     }
@@ -188,20 +189,14 @@ fn test_complex_namespace() {
     let schema = Schema::parse_str(schema).unwrap();
     assert_eq!(schema, TestComplexNamespace::get_schema());
     if let Schema::Record(RecordSchema { name, fields, .. }) = TestComplexNamespace::get_schema() {
-        assert_eq!(
-            "com.testing.complex.namespace".to_owned(),
-            name.namespace.unwrap()
-        );
+        assert_eq!(Some("com.testing.complex.namespace"), name.namespace());
         let inner_schema = fields
             .iter()
             .filter(|field| field.name == "a")
             .map(|field| &field.schema)
             .next();
         if let Some(Schema::Record(RecordSchema { name, .. })) = inner_schema {
-            assert_eq!(
-                "com.testing.namespace".to_owned(),
-                name.namespace.clone().unwrap()
-            )
+            assert_eq!(Some("com.testing.namespace"), name.namespace())
         } else {
             panic!("Field 'a' must have a record schema")
         }
@@ -239,8 +234,8 @@ fn avro_rs_239_test_named_record() {
     let schema = Schema::parse_str(schema).unwrap();
     assert_eq!(schema, TestNamedRecord::get_schema());
     if let Schema::Record(RecordSchema { name, .. }) = TestNamedRecord::get_schema() {
-        assert_eq!("Other", name.name.as_str());
-        assert_eq!(Some("com.testing.namespace"), name.namespace.as_deref())
+        assert_eq!("Other", name.name());
+        assert_eq!(Some("com.testing.namespace"), name.namespace())
     } else {
         panic!("TestNamedRecord schema must be a record schema")
     }
@@ -1024,7 +1019,7 @@ fn test_basic_with_attributes() {
     "#;
     let schema = Schema::parse_str(schema).unwrap();
     if let Schema::Record(RecordSchema { name, doc, .. }) = TestBasicWithAttributes::get_schema() {
-        assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
+        assert_eq!(Some("com.testing.namespace"), name.namespace());
         assert_eq!("A Documented Record", doc.unwrap())
     } else {
         panic!("TestBasicWithAttributes schema must be a record schema")
@@ -1067,7 +1062,7 @@ fn test_basic_with_out_doc_attributes() {
     let derived_schema = TestBasicWithOuterDocAttributes::get_schema();
     assert_eq!(&schema, &derived_schema);
     if let Schema::Record(RecordSchema { name, doc, .. }) = derived_schema {
-        assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
+        assert_eq!(Some("com.testing.namespace"), name.namespace());
         assert_eq!("A Documented Record", doc.unwrap())
     } else {
         panic!("TestBasicWithOuterDocAttributes schema must be a record schema")
@@ -1109,7 +1104,7 @@ fn test_basic_with_large_doc() {
     "#;
     let schema = Schema::parse_str(schema).unwrap();
     if let Schema::Record(RecordSchema { name, doc, .. }) = TestBasicWithLargeDoc::get_schema() {
-        assert_eq!("com.testing.namespace".to_owned(), name.namespace.unwrap());
+        assert_eq!(Some("com.testing.namespace"), name.namespace());
         assert_eq!(
             "A Documented Record\nthat spans\nmultiple lines",
             doc.unwrap()
@@ -1860,15 +1855,19 @@ fn avro_rs_397_with() {
     )
     .unwrap();
 
-    fn long_schema(_named_schemas: &mut HashSet<Name>, _enclosing_namespace: &Namespace) -> Schema {
+    fn long_schema(
+        _named_schemas: &mut HashSet<Name>,
+        _enclosing_namespace: NamespaceRef,
+    ) -> Schema {
         Schema::Long
     }
 
     mod module {
         use super::*;
+        use apache_avro::schema::NamespaceRef;
         pub fn get_schema_in_ctxt(
             _named_schemas: &mut HashSet<Name>,
-            _enclosing_namespace: &Namespace,
+            _enclosing_namespace: NamespaceRef,
         ) -> Schema {
             Schema::Bytes
         }
@@ -1913,10 +1912,10 @@ fn avro_rs_397_with_generic() {
 
     fn generic<const N: usize>(
         _named_schemas: &mut HashSet<Name>,
-        _enclosing_namespace: &Namespace,
+        _enclosing_namespace: NamespaceRef,
     ) -> Schema {
         Schema::Fixed(FixedSchema {
-            name: Name::new(&format!("fixed_{N}")).unwrap(),
+            name: Name::new(format!("fixed_{N}")).unwrap(),
             aliases: None,
             doc: None,
             size: N,
@@ -2004,7 +2003,10 @@ fn avro_rs_397_derive_with_expr_lambda() {
 
 #[test]
 fn avro_rs_398_transparent_with_skip() {
-    fn long_schema(_named_schemas: &mut HashSet<Name>, _enclosing_namespace: &Namespace) -> Schema {
+    fn long_schema(
+        _named_schemas: &mut HashSet<Name>,
+        _enclosing_namespace: NamespaceRef,
+    ) -> Schema {
         Schema::Long
     }
 
@@ -2310,7 +2312,7 @@ fn avro_rs_448_transparent_with() {
 
     let mut named_schemas = HashSet::new();
     assert_eq!(
-        TestStruct::get_record_fields_in_ctxt(&mut named_schemas, &None),
+        TestStruct::get_record_fields_in_ctxt(&mut named_schemas, None),
         None
     );
     assert!(
@@ -2335,21 +2337,21 @@ fn avro_rs_448_transparent_with_2() {
     }
 
     let mut named_schemas = HashSet::new();
-    let fields = TestStruct::get_record_fields_in_ctxt(&mut named_schemas, &None).unwrap();
+    let fields = TestStruct::get_record_fields_in_ctxt(&mut named_schemas, None).unwrap();
     assert!(
         named_schemas.is_empty(),
         "No name should've been added: {named_schemas:?}"
     );
     assert_eq!(fields.len(), 2);
 
-    TestStruct::get_schema_in_ctxt(&mut named_schemas, &None);
+    TestStruct::get_schema_in_ctxt(&mut named_schemas, None);
     assert_eq!(
         named_schemas.len(),
         1,
         "One name should've been added: {named_schemas:?}"
     );
 
-    let fields = TestStruct::get_record_fields_in_ctxt(&mut named_schemas, &None).unwrap();
+    let fields = TestStruct::get_record_fields_in_ctxt(&mut named_schemas, None).unwrap();
     assert_eq!(
         named_schemas.len(),
         1,