diff --git a/netcdf/src/dimension.rs b/netcdf/src/dimension.rs
index a259661..fb81217 100644
--- a/netcdf/src/dimension.rs
+++ b/netcdf/src/dimension.rs
@@ -7,6 +7,118 @@ use netcdf_sys::*;
 
 use super::error;
 
+mod sealed {
+    pub trait Sealed {}
+}
+
+/// Types which can be used to distinguish dimensions
+/// in a netCDF file. This can be `&str` (the normal use case)
+/// or a special identifier when using nested groups.
+///
+/// This trait is not expected to be implemented elsewhere and is therefore sealed.
+///
+/// # Examples
+/// (helper function to show consumption of type)
+/// ```rust,no_run
+/// # use netcdf::AsNcDimensions;
+/// fn take(d: impl AsNcDimensions) {}
+/// ```
+/// Normally one uses the name of the dimension to specify the dimension
+/// ```rust,no_run
+/// # use netcdf::AsNcDimensions;
+/// # fn take(d: impl AsNcDimensions) {}
+/// take(()); // scalar
+/// take("x"); // single dimension
+/// take(["x", "y"]); // multiple dimensions
+/// ```
+/// When working with dimensions across groups, it might be necessary
+/// to use dimension identifiers to get the correct group dimension
+/// ```rust,no_run
+/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+/// # use netcdf::AsNcDimensions;
+/// # fn take(d: impl AsNcDimensions) {}
+/// let file = netcdf::open("test.nc")?;
+/// let dim = file.dimension("x").expect("File does not contain dimension");
+/// let dimid = dim.identifier();
+///
+/// take(dimid); // from a dimension identifier
+/// take([dimid, dimid]); // from multiple identifiers
+/// # Ok(()) }
+/// ```
+pub trait AsNcDimensions: sealed::Sealed {
+    /// Convert from a slice of [`&str`]/[`DimensionIdentifier`] to concrete dimensions
+    /// which are guaranteed to exist in this file
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>>;
+}
+
+impl sealed::Sealed for &[&str] {}
+impl AsNcDimensions for &[&str] {
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        self.iter()
+            .map(|&name| match dimension_from_name(ncid, name) {
+                Ok(Some(x)) => Ok(x),
+                Ok(None) => Err(format!("Dimension {name} not found").into()),
+                Err(e) => Err(e),
+            })
+            .collect()
+    }
+}
+impl<const N: usize> sealed::Sealed for [&str; N] {}
+impl<const N: usize> AsNcDimensions for [&str; N] {
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        self.as_slice().get_dimensions(ncid)
+    }
+}
+impl<const N: usize> sealed::Sealed for &[&str; N] {}
+impl<const N: usize> AsNcDimensions for &[&str; N] {
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        self.as_slice().get_dimensions(ncid)
+    }
+}
+
+impl sealed::Sealed for &[DimensionIdentifier] {}
+impl AsNcDimensions for &[DimensionIdentifier] {
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        self.iter()
+            .map(|dimid| match dimension_from_identifier(ncid, *dimid) {
+                Ok(Some(x)) => Ok(x),
+                Ok(None) => Err("Dimension id does not exist".into()),
+                Err(e) => Err(e),
+            })
+            .collect()
+    }
+}
+impl<const N: usize> sealed::Sealed for [DimensionIdentifier; N] {}
+impl<const N: usize> AsNcDimensions for [DimensionIdentifier; N] {
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        self.as_slice().get_dimensions(ncid)
+    }
+}
+impl<const N: usize> sealed::Sealed for &[DimensionIdentifier; N] {}
+impl<const N: usize> AsNcDimensions for &[DimensionIdentifier; N] {
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        self.as_slice().get_dimensions(ncid)
+    }
+}
+impl sealed::Sealed for () {}
+impl AsNcDimensions for () {
+    fn get_dimensions<'g>(&self, _ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        Ok(Vec::new())
+    }
+}
+impl sealed::Sealed for &str {}
+impl AsNcDimensions for &str {
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        ([*self]).get_dimensions(ncid)
+    }
+}
+impl sealed::Sealed for DimensionIdentifier {}
+impl AsNcDimensions for DimensionIdentifier {
+    fn get_dimensions<'g>(&self, ncid: nc_type) -> error::Result<Vec<Dimension<'g>>> {
+        ([*self]).get_dimensions(ncid)
+    }
+}
+
 /// Represents a netcdf dimension
 #[derive(Debug, Clone)]
 pub struct Dimension<'g> {
@@ -25,9 +137,23 @@ pub struct DimensionIdentifier {
     pub(crate) dimid: nc_type,
 }
 
+impl DimensionIdentifier {
+    // Internal netcdf detail, the top 16 bits gives the corresponding
+    // file handle. This to ensure dimensions are not added from another
+    // file which is unrelated to self
+    pub(crate) fn belongs_to(&self, ncid: nc_type) -> bool {
+        self.ncid >> 16 == ncid >> 16
+    }
+}
+
 #[allow(clippy::len_without_is_empty)]
 impl<'g> Dimension<'g> {
     /// Get current length of this dimension
+    ///
+    /// ## Note
+    /// A dimension can be unlimited (growable) and changes size
+    /// if putting values to a variable which uses this
+    /// dimension
     pub fn len(&self) -> usize {
         if let Some(x) = self.len {
             x.get()
@@ -66,7 +192,10 @@ impl<'g> Dimension<'g> {
     }
 
     /// Grabs the unique identifier for this dimension, which
-    /// can be used in `add_variable_from_identifiers`
+    /// can be used in [`add_variable`](crate::FileMut::add_variable).
+    ///
+    /// This is useful when working with nested groups and need
+    /// to distinguish between names at different levels.
     pub fn identifier(&self) -> DimensionIdentifier {
         self.id
     }
@@ -272,6 +401,47 @@ pub(crate) fn dimension_from_name<'f>(
     }))
 }
 
+pub(crate) fn dimension_from_identifier<'f>(
+    ncid: nc_type,
+    dimid: DimensionIdentifier,
+) -> error::Result<Option<Dimension<'f>>> {
+    if !dimid.belongs_to(ncid) {
+        return Err(error::Error::WrongDataset);
+    }
+    let dimid = dimid.dimid;
+
+    let mut dimlen = 0;
+    unsafe {
+        error::checked(super::with_lock(|| nc_inq_dimlen(ncid, dimid, &mut dimlen))).unwrap();
+    }
+    if dimlen != 0 {
+        // Have to check if this dimension is unlimited
+        let mut nunlim = 0;
+        unsafe {
+            error::checked(super::with_lock(|| {
+                nc_inq_unlimdims(ncid, &mut nunlim, std::ptr::null_mut())
+            }))?;
+        }
+        if nunlim != 0 {
+            let mut unlimdims = Vec::with_capacity(nunlim.try_into()?);
+            unsafe {
+                error::checked(super::with_lock(|| {
+                    nc_inq_unlimdims(ncid, std::ptr::null_mut(), unlimdims.as_mut_ptr())
+                }))?;
+            }
+            unsafe { unlimdims.set_len(nunlim.try_into()?) }
+            if unlimdims.contains(&dimid) {
+                dimlen = 0;
+            }
+        }
+    }
+    Ok(Some(Dimension {
+        len: core::num::NonZeroUsize::new(dimlen),
+        id: super::dimension::DimensionIdentifier { ncid, dimid },
+        _group: PhantomData,
+    }))
+}
+
 pub(crate) fn add_dimension_at<'f>(
     ncid: nc_type,
     name: &str,
diff --git a/netcdf/src/file.rs b/netcdf/src/file.rs
index 17a8029..4e9b3f3 100644
--- a/netcdf/src/file.rs
+++ b/netcdf/src/file.rs
@@ -7,7 +7,7 @@ use std::path;
 use netcdf_sys::*;
 
 use super::attribute::{Attribute, AttributeValue};
-use super::dimension::{self, Dimension};
+use super::dimension::{AsNcDimensions, Dimension};
 use super::error;
 use super::group::{Group, GroupMut};
 use super::variable::{NcPutGet, Variable, VariableMut};
@@ -338,20 +338,50 @@ impl FileMut {
         ))
     }
 
-    /// Create a Variable into the dataset, with no data written into it
+    /// Create a variable in the dataset, with no data written into it
     ///
-    /// Dimensions are identified using the name of the dimension, and will recurse upwards
-    /// if not found in the current group.
-    pub fn add_variable<'f, T>(
+    /// # Examples
+    /// ## Adding variables with different types
+    /// ```rust,no_run
+    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+    /// let mut file = netcdf::create("file.nc")?;
+    /// file.add_dimension("x", 10)?;
+    /// file.add_dimension("y", 11)?;
+    /// file.add_variable::<u8, _>("var_u8", &["y", "x"])?;
+    /// file.add_variable::<i8, _>("var_i8", &["y", "x"])?;
+    /// file.add_variable::<u64, _>("var_u64", &["y", "x"])?;
+    /// file.add_variable::<f64, _>("var_f64", &["y", "x"])?;
+    /// # Ok(())}
+    /// ```
+    /// ## Adding a scalar variable
+    /// ```rust,no_run
+    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+    /// let mut file = netcdf::create("scalar.nc")?;
+    /// file.add_variable::<u8, _>("var2", ())?;
+    /// # Ok(())}
+    /// ```
+    /// ## Using dimension identifiers in place of names
+    /// ```rust,no_run
+    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
+    /// let mut file = netcdf::create("dimids.nc")?;
+    /// let dimx = file.dimension("x").unwrap().identifier();
+    /// let dimy = file.dimension("y").unwrap().identifier();
+    /// file.add_variable::<u8, _>("var2", &[dimy, dimx])?;
+    /// # Ok(())}
+    ///```
+    ///
+    /// See [`AsNcDimensions`] for how to specify dimensions.
+    pub fn add_variable<'f, T, D>(
         &'f mut self,
         name: &str,
-        dims: &[&str],
+        dims: D,
     ) -> error::Result<VariableMut<'f>>
     where
         T: NcPutGet,
+        D: AsNcDimensions,
     {
         let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
-        VariableMut::add_from_str(ncid, T::NCTYPE, name, dims)
+        VariableMut::add_from_dimids(ncid, T::NCTYPE, name, dims.get_dimensions(ncid)?)
     }
 
     /// Create a variable with the specified type
@@ -410,19 +440,6 @@ impl FileMut {
         let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
         VariableMut::add_from_str(ncid, NC_STRING, name, dims)
     }
-    /// Adds a variable from a set of unique identifiers, recursing upwards
-    /// from the current group if necessary.
-    pub fn add_variable_from_identifiers<'f, T>(
-        &'f mut self,
-        name: &str,
-        dims: &[dimension::DimensionIdentifier],
-    ) -> error::Result<VariableMut<'f>>
-    where
-        T: NcPutGet,
-    {
-        let (ncid, name) = super::group::get_parent_ncid_and_stem(self.ncid(), name)?;
-        super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE)
-    }
 }
 
 #[cfg(feature = "has-mmap")]
diff --git a/netcdf/src/group.rs b/netcdf/src/group.rs
index a821cb6..324b203 100644
--- a/netcdf/src/group.rs
+++ b/netcdf/src/group.rs
@@ -6,7 +6,7 @@ use std::marker::PhantomData;
 use netcdf_sys::*;
 
 use super::attribute::{Attribute, AttributeValue};
-use super::dimension::Dimension;
+use super::dimension::{AsNcDimensions, Dimension};
 use super::error;
 use super::variable::{NcPutGet, Variable, VariableMut};
 
@@ -244,18 +244,23 @@ impl<'f> GroupMut<'f> {
     /// Create a Variable into the dataset, with no data written into it
     ///
     /// Dimensions are identified using the name of the dimension, and will recurse upwards
-    /// if not found in the current group.
-    pub fn add_variable<'g, T>(
+    /// if not found in the current group. If the name is shadowed one can get an
+    /// [`DimensionIdentifier`](crate::DimensionIdentifier) to ensure the right dimension
+    /// is used
+    ///
+    /// See [`AsNcDimensions`] for how to specify dimensions.
+    pub fn add_variable<'g, T, D>(
         &'g mut self,
         name: &str,
-        dims: &[&str],
+        dims: D,
     ) -> error::Result<VariableMut<'g>>
     where
         T: NcPutGet,
         'f: 'g,
+        D: AsNcDimensions,
     {
         let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
-        VariableMut::add_from_str(ncid, T::NCTYPE, name, dims)
+        VariableMut::add_from_dimids(ncid, T::NCTYPE, name, dims.get_dimensions(ncid)?)
     }
     /// Adds a variable with a basic type of string
     pub fn add_string_variable<'g>(
@@ -266,19 +271,6 @@ impl<'f> GroupMut<'f> {
         let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
         VariableMut::add_from_str(ncid, NC_STRING, name, dims)
     }
-    /// Adds a variable from a set of unique identifiers, recursing upwards
-    /// from the current group if necessary.
-    pub fn add_variable_from_identifiers<'g, T>(
-        &'g mut self,
-        name: &str,
-        dims: &[super::dimension::DimensionIdentifier],
-    ) -> error::Result<VariableMut<'g>>
-    where
-        T: NcPutGet,
-    {
-        let (ncid, name) = super::group::get_parent_ncid_and_stem(self.id(), name)?;
-        super::variable::add_variable_from_identifiers(ncid, name, dims, T::NCTYPE)
-    }
 
     /// Create a variable with the specified type
     pub fn add_variable_with_type(
diff --git a/netcdf/src/lib.rs b/netcdf/src/lib.rs
index ae9df16..134bf1a 100644
--- a/netcdf/src/lib.rs
+++ b/netcdf/src/lib.rs
@@ -71,7 +71,7 @@
 //! file.add_unlimited_dimension("time")?;
 //!
 //! // A variable can now be declared, and must be created from the dimension names.
-//! let mut var = file.add_variable::<i32>(
+//! let mut var = file.add_variable::<i32, _>(
 //!             "crab_coolness_level",
 //!             &["time", "ncrabs"],
 //! )?;
@@ -114,7 +114,7 @@ pub mod types;
 pub(crate) mod variable;
 
 pub use attribute::{Attribute, AttributeValue};
-pub use dimension::{Dimension, DimensionIdentifier};
+pub use dimension::{AsNcDimensions, Dimension, DimensionIdentifier};
 pub use error::{Error, Result};
 pub use extent::{Extent, Extents};
 #[cfg(feature = "has-mmap")]
diff --git a/netcdf/src/variable.rs b/netcdf/src/variable.rs
index fc8af9d..a8da66e 100644
--- a/netcdf/src/variable.rs
+++ b/netcdf/src/variable.rs
@@ -1434,6 +1434,42 @@ impl<'g> VariableMut<'g> {
 }
 
 impl<'g> VariableMut<'g> {
+    pub(crate) fn add_from_dimids<'d>(
+        ncid: nc_type,
+        xtype: nc_type,
+        name: &str,
+        dimensions: Vec<Dimension<'d>>,
+    ) -> error::Result<Self>
+    where
+        'd: 'g,
+    {
+        let cname = super::utils::short_name_to_bytes(name)?;
+        let mut varid = 0;
+        unsafe {
+            let dims = dimensions.iter().map(|x| x.id.dimid).collect::<Vec<_>>();
+            let dimlen = dims.len().try_into()?;
+            error::checked(super::with_lock(|| {
+                nc_def_var(
+                    ncid,
+                    cname.as_ptr().cast(),
+                    xtype,
+                    dimlen,
+                    dims.as_ptr(),
+                    &mut varid,
+                )
+            }))?;
+        }
+        Ok(VariableMut(
+            Variable {
+                ncid,
+                varid,
+                vartype: xtype,
+                dimensions,
+                _group: PhantomData,
+            },
+            PhantomData,
+        ))
+    }
     pub(crate) fn add_from_str(
         ncid: nc_type,
         xtype: nc_type,
@@ -1520,62 +1556,3 @@ pub(crate) fn variables_at_ncid<'g>(
         })
     }))
 }
-
-pub(crate) fn add_variable_from_identifiers<'g>(
-    ncid: nc_type,
-    name: &str,
-    dims: &[super::dimension::DimensionIdentifier],
-    xtype: nc_type,
-) -> error::Result<VariableMut<'g>> {
-    let cname = super::utils::short_name_to_bytes(name)?;
-
-    let dimensions = dims
-        .iter()
-        .map(move |&id| {
-            // Internal netcdf detail, the top 16 bits gives the corresponding
-            // file handle. This to ensure dimensions are not added from another
-            // file which is unrelated to self
-            if id.ncid >> 16 != ncid >> 16 {
-                return Err(error::Error::WrongDataset);
-            }
-            let mut dimlen = 0;
-            unsafe {
-                error::checked(super::with_lock(|| {
-                    nc_inq_dimlen(id.ncid, id.dimid, &mut dimlen)
-                }))?;
-            }
-            Ok(Dimension {
-                len: core::num::NonZeroUsize::new(dimlen),
-                id,
-                _group: PhantomData,
-            })
-        })
-        .collect::<error::Result<Vec<_>>>()?;
-    let dims = dims.iter().map(|x| x.dimid).collect::<Vec<_>>();
-
-    let mut varid = 0;
-    unsafe {
-        let dimlen = dims.len().try_into()?;
-        error::checked(super::with_lock(|| {
-            nc_def_var(
-                ncid,
-                cname.as_ptr().cast(),
-                xtype,
-                dimlen,
-                dims.as_ptr(),
-                &mut varid,
-            )
-        }))?;
-    }
-
-    Ok(VariableMut(
-        Variable {
-            ncid,
-            dimensions,
-            varid,
-            vartype: xtype,
-            _group: PhantomData,
-        },
-        PhantomData,
-    ))
-}
diff --git a/netcdf/tests/attributes.rs b/netcdf/tests/attributes.rs
index d7f9b0e..199b40a 100644
--- a/netcdf/tests/attributes.rs
+++ b/netcdf/tests/attributes.rs
@@ -23,7 +23,7 @@ fn attributes_read() {
     let mut file = netcdf::create(path).expect("Could not create file");
 
     let var = &mut file
-        .add_variable::<f32>("var", &[])
+        .add_variable::<f32, _>("var", ())
         .expect("Could not add variable");
     var.put_attribute("att", "some attribute")
         .expect("Could not add attribute");
diff --git a/netcdf/tests/file.rs b/netcdf/tests/file.rs
index 78fa4cb..73f34b8 100644
--- a/netcdf/tests/file.rs
+++ b/netcdf/tests/file.rs
@@ -52,7 +52,7 @@ fn fetch_from_path() {
         let mut group = file.add_group("grp").unwrap();
         let mut subgroup = group.add_group("subgrp").unwrap();
         subgroup.add_dimension("dim", 1).unwrap();
-        subgroup.add_variable::<f64>("var", &["dim"]).unwrap();
+        subgroup.add_variable::<f64, _>("var", &["dim"]).unwrap();
         subgroup.add_attribute("attr", "test").unwrap();
     }
     let file = netcdf::open(path).unwrap();
diff --git a/netcdf/tests/group.rs b/netcdf/tests/group.rs
index ef45637..b90fa51 100644
--- a/netcdf/tests/group.rs
+++ b/netcdf/tests/group.rs
@@ -44,10 +44,10 @@ fn find_variable() {
     let mut file = netcdf::create(path).unwrap();
     let mut group = file.add_group("group").unwrap();
 
-    group.add_variable::<u8>("v", &[]).unwrap();
-    group.add_variable::<u8>("w", &[]).unwrap();
+    group.add_variable::<u8, _>("v", ()).unwrap();
+    group.add_variable::<u8, _>("w", ()).unwrap();
     group.add_dimension("d", 3).unwrap();
-    group.add_variable::<u8>("z", &["d"]).unwrap();
+    group.add_variable::<u8, _>("z", &["d"]).unwrap();
 
     assert_eq!(group.variables_mut().count(), 3);
     assert_eq!(group.variables().count(), 3);
@@ -83,8 +83,8 @@ fn add_and_get_from_path() {
         file.add_group("a/b").unwrap();
         file.add_dimension("a/b/dim", 1).unwrap();
         assert!(file.add_dimension("a/c/dim", 1).is_err());
-        file.add_variable::<f64>("a/b/var", &["dim"]).unwrap();
-        assert!(file.add_variable::<f64>("a/c/var", &["dim"]).is_err());
+        file.add_variable::<f64, _>("a/b/var", &["dim"]).unwrap();
+        assert!(file.add_variable::<f64, _>("a/c/var", &["dim"]).is_err());
         file.add_attribute("a/b/attr", "test").unwrap();
         assert!(file.add_attribute("a/c/test", "test").is_err());
     }
diff --git a/netcdf/tests/lib.rs b/netcdf/tests/lib.rs
index 8913db7..ba8d547 100644
--- a/netcdf/tests/lib.rs
+++ b/netcdf/tests/lib.rs
@@ -134,10 +134,10 @@ fn variable_not_replacing() {
     let p = d.path().join("variable_not_replacing.nc");
     let mut f = netcdf::create(p).unwrap();
 
-    f.add_variable::<u16>("a", &[]).unwrap();
-    f.add_variable::<i16>("b", &[]).unwrap();
-    f.add_variable::<u8>("a", &[]).unwrap_err();
-    f.add_variable_from_identifiers::<i8>("b", &[]).unwrap_err();
+    f.add_variable::<u16, _>("a", ()).unwrap();
+    f.add_variable::<i16, _>("b", ()).unwrap();
+    f.add_variable::<u8, _>("a", ()).unwrap_err();
+    f.add_variable::<i8, _>("b", ()).unwrap_err();
 }
 
 #[test]
@@ -179,10 +179,10 @@ fn netcdf_error() {
     let path = d.path().join("netcdf_error.nc");
     let mut file = netcdf::create(path).expect("Could not create file");
 
-    file.add_variable::<i8>("var", &["v"]).unwrap_err();
+    file.add_variable::<i8, _>("var", &["v"]).unwrap_err();
     file.add_dimension("v", 3).expect("Could not add dimension");
-    file.add_variable::<i8>("var", &["v"]).unwrap();
-    file.add_variable::<i8>("var", &["v"]).unwrap_err();
+    file.add_variable::<i8, _>("var", &["v"]).unwrap();
+    file.add_variable::<i8, _>("var", &["v"]).unwrap_err();
 
     file.add_dimension("v", 2).unwrap_err();
     file.add_unlimited_dimension("v").unwrap_err();
@@ -258,13 +258,13 @@ fn create_group_dimensions() {
     let g = &mut f.add_group("gp1").unwrap();
 
     g.add_dimension("x", 100).unwrap();
-    g.add_variable::<u8>("y", &["x"]).unwrap();
+    g.add_variable::<u8, _>("y", &["x"]).unwrap();
 
     let gg = &mut g.add_group("gp2").unwrap();
-    gg.add_variable::<i8>("y", &["x"]).unwrap();
+    gg.add_variable::<i8, _>("y", &["x"]).unwrap();
 
     gg.add_dimension("x", 30).unwrap();
-    gg.add_variable::<i8>("z", &["x"]).unwrap();
+    gg.add_variable::<i8, _>("z", &["x"]).unwrap();
 
     assert_eq!(
         f.group("gp1")
@@ -342,7 +342,7 @@ fn def_dims_vars_attrs() {
         let var_name = "varstuff_int";
         let data: Vec<i32> = vec![42; 10 * 20];
         let var = &mut file
-            .add_variable::<i32>(var_name, &[dim1_name, dim2_name])
+            .add_variable::<i32, _>(var_name, &[dim1_name, dim2_name])
             .unwrap();
         var.put_values(data.as_slice(), ..).unwrap();
         assert_eq!(var.dimensions()[0].len(), 10);
@@ -350,7 +350,7 @@ fn def_dims_vars_attrs() {
 
         let var_name = "varstuff_float";
         let data: Vec<f32> = vec![42.2; 10];
-        let mut var = file.add_variable::<f32>(var_name, &[dim1_name]).unwrap();
+        let mut var = file.add_variable::<f32, _>(var_name, &[dim1_name]).unwrap();
         var.put_values(data.as_slice(), ..).unwrap();
         assert_eq!(var.dimensions()[0].len(), 10);
 
@@ -456,60 +456,60 @@ fn all_var_types() {
         // byte
         let data = vec![42i8; 10];
         let var_name = "var_byte";
-        let mut var = root.add_variable::<i8>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<i8, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         let data = vec![42u8; 10];
         let var_name = "var_char";
-        let mut var = root.add_variable::<u8>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<u8, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         // short
         let data = vec![42i16; 10];
         let var_name = "var_short";
-        let mut var = root.add_variable::<i16>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<i16, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         // ushort
         let data = vec![42u16; 10];
         let var_name = "var_ushort";
-        let mut var = root.add_variable::<u16>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<u16, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         // int
         let data = vec![42i32; 10];
         let var_name = "var_int";
-        let mut var = root.add_variable::<i32>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<i32, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         // uint
         let data = vec![42u32; 10];
         let var_name = "var_uint";
-        let mut var = root.add_variable::<u32>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<u32, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         // int64
         let data = vec![42i64; 10];
         let var_name = "var_int64";
-        let mut var = root.add_variable::<i64>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<i64, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         // uint64
         let data = vec![42u64; 10];
         let var_name = "var_uint64";
-        let mut var = root.add_variable::<u64>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<u64, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         // float
         let data = vec![42.2f32; 10];
         let var_name = "var_float";
-        let mut var = root.add_variable::<f32>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<f32, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
 
         // double
         let data = vec![42.2f64; 10];
         let var_name = "var_double";
-        let mut var = root.add_variable::<f64>(var_name, &[dim_name]).unwrap();
+        let mut var = root.add_variable::<f64, _>(var_name, &[dim_name]).unwrap();
         var.put_values(&data, ..).unwrap();
     }
 
@@ -625,7 +625,7 @@ fn append() {
         let mut file_w = netcdf::create(&f).unwrap();
         file_w.add_dimension(dim_name, 3).unwrap();
         let var = &mut file_w
-            .add_variable::<i32>("some_variable", &[dim_name])
+            .add_variable::<i32, _>("some_variable", &[dim_name])
             .unwrap();
         var.put_values::<i32, _>(&[1, 2, 3], ..).unwrap();
         // close it (done when `file_w` goes out of scope)
@@ -635,7 +635,7 @@ fn append() {
         // and create a variable called "some_other_variable"
         let mut file_a = netcdf::append(&f).unwrap();
         let var = &mut file_a
-            .add_variable::<i32>("some_other_variable", &[dim_name])
+            .add_variable::<i32, _>("some_other_variable", &[dim_name])
             .unwrap();
         var.put_values::<i32, _>(&[4, 5, 6], ..).unwrap();
         // close it (done when `file_a` goes out of scope)
@@ -659,7 +659,9 @@ fn put_single_value() {
         // and create a variable called "some_variable" in it
         let mut file_w = netcdf::create(&f).unwrap();
         file_w.add_dimension(dim_name, 3).unwrap();
-        let var = &mut file_w.add_variable::<f32>(var_name, &[dim_name]).unwrap();
+        let var = &mut file_w
+            .add_variable::<f32, _>(var_name, &[dim_name])
+            .unwrap();
         var.put_values(&[1., 2., 3.], ..).unwrap();
     }
     let indices: [usize; 1] = [0];
@@ -689,7 +691,9 @@ fn put_values() {
         // and create a variable called "some_variable" in it
         let mut file_w = netcdf::create(&f).unwrap();
         file_w.add_dimension(dim_name, 3).unwrap();
-        let var = &mut file_w.add_variable::<i32>(var_name, &[dim_name]).unwrap();
+        let var = &mut file_w
+            .add_variable::<i32, _>(var_name, &[dim_name])
+            .unwrap();
         var.put_values(&[1i32, 2, 3], ..).unwrap();
         // close it (done when `file_w` goes out of scope)
     }
@@ -724,7 +728,9 @@ fn set_fill_value() {
 
     let mut file_w = netcdf::create(f).unwrap();
     file_w.add_dimension(dim_name, 3).unwrap();
-    let var = &mut file_w.add_variable::<i32>(var_name, &[dim_name]).unwrap();
+    let var = &mut file_w
+        .add_variable::<i32, _>(var_name, &[dim_name])
+        .unwrap();
     var.set_fill_value(fill_value).unwrap();
 
     var.put_values(&[2, 3], &[1..]).unwrap();
@@ -758,7 +764,7 @@ fn more_fill_values() {
     let mut file = netcdf::create(path).expect("Could not open file");
 
     file.add_dimension("x", 2).unwrap();
-    let var = &mut file.add_variable::<i32>("v0", &["x"]).unwrap();
+    let var = &mut file.add_variable::<i32, _>("v0", &["x"]).unwrap();
     var.set_fill_value(1_i32).unwrap();
 
     var.put_value(6, [1]).unwrap();
@@ -767,7 +773,7 @@ fn more_fill_values() {
     assert_eq!(var.get_value::<i32, _>([0]).unwrap(), 1_i32);
     assert_eq!(var.get_value::<i32, _>([1]).unwrap(), 6_i32);
 
-    let var = &mut file.add_variable::<i32>("v1", &["x"]).unwrap();
+    let var = &mut file.add_variable::<i32, _>("v1", &["x"]).unwrap();
     unsafe {
         var.set_nofill().unwrap();
     }
@@ -778,7 +784,7 @@ fn more_fill_values() {
     assert_eq!(var.get_value::<i32, _>([1]).unwrap(), 6_i32);
     assert!(var.attribute("_FillValue").is_none());
 
-    let var = &mut file.add_variable::<i32>("v2", &["x"]).unwrap();
+    let var = &mut file.add_variable::<i32, _>("v2", &["x"]).unwrap();
     var.set_fill_value(2_i32).unwrap();
     assert_eq!(
         var.attribute("_FillValue").unwrap().value().unwrap(),
@@ -842,7 +848,7 @@ fn use_compression_chunking() {
 
     file.add_dimension("x", 10).unwrap();
 
-    let var = &mut file.add_variable::<i32>("compressed", &["x"]).unwrap();
+    let var = &mut file.add_variable::<i32, _>("compressed", &["x"]).unwrap();
     var.set_compression(5, false).unwrap();
     var.set_chunking(&[5]).unwrap();
 
@@ -850,21 +856,21 @@ fn use_compression_chunking() {
     var.put_values(&v, ..).unwrap();
 
     let var = &mut file
-        .add_variable::<i32>("compressed2", &["x", "x"])
+        .add_variable::<i32, _>("compressed2", &["x", "x"])
         .unwrap();
     var.set_compression(9, true).unwrap();
     var.set_chunking(&[5, 5]).unwrap();
     var.put_values(&[1i32, 2, 3, 4, 5, 6, 7, 8, 9, 10], (..10, ..1))
         .unwrap();
 
-    let var = &mut file.add_variable::<i32>("chunked3", &["x"]).unwrap();
+    let var = &mut file.add_variable::<i32, _>("chunked3", &["x"]).unwrap();
     assert!(matches!(
         var.set_chunking(&[2, 2]).unwrap_err(),
         netcdf::Error::SliceLen
     ));
 
     file.add_dimension("y", 0).unwrap();
-    let var = &mut file.add_variable::<u8>("chunked4", &["y", "x"]).unwrap();
+    let var = &mut file.add_variable::<u8, _>("chunked4", &["y", "x"]).unwrap();
 
     var.set_chunking(&[100, 2]).unwrap();
 }
@@ -879,13 +885,13 @@ fn set_compression_all_variables_in_a_group() {
         .expect("Could not create dimension");
     file.add_dimension("y", 15)
         .expect("Could not create dimension");
-    file.add_variable::<u8>("var0", &["x", "y"])
+    file.add_variable::<u8, _>("var0", &["x", "y"])
         .expect("Could not create variable");
-    file.add_variable::<u8>("var1", &["x", "y"])
+    file.add_variable::<u8, _>("var1", &["x", "y"])
         .expect("Could not create variable");
-    file.add_variable::<u8>("var2", &["x", "y"])
+    file.add_variable::<u8, _>("var2", &["x", "y"])
         .expect("Could not create variable");
-    file.add_variable::<u8>("var3", &["x", "y"])
+    file.add_variable::<u8, _>("var3", &["x", "y"])
         .expect("Could not create variable");
 
     for mut var in file.variables_mut() {
@@ -939,9 +945,9 @@ fn add_conflicting_variables() {
     file.add_dimension("x", 10).unwrap();
     file.add_dimension("y", 20).unwrap();
 
-    file.add_variable::<i32>("x", &["x"]).unwrap();
+    file.add_variable::<i32, _>("x", &["x"]).unwrap();
 
-    let e = file.add_variable::<f32>("x", &["y"]).unwrap_err();
+    let e = file.add_variable::<f32, _>("x", &["y"]).unwrap_err();
     assert!(match e {
         netcdf::Error::AlreadyExists => {
             true
@@ -961,7 +967,7 @@ fn unlimited_dimension_single_putting() {
     file.add_unlimited_dimension("x").unwrap();
     file.add_unlimited_dimension("y").unwrap();
 
-    let var = &mut file.add_variable::<u8>("var", &["x", "y"]).unwrap();
+    let var = &mut file.add_variable::<u8, _>("var", &["x", "y"]).unwrap();
     var.set_fill_value(0u8).unwrap();
 
     var.put_value(1, (0, 0)).unwrap();
@@ -1005,7 +1011,9 @@ fn unlimited_dimension_multi_putting() {
     file.add_unlimited_dimension("x3").unwrap();
     file.add_unlimited_dimension("x4").unwrap();
 
-    let var = &mut file.add_variable::<u8>("one_unlim", &["x", "z"]).unwrap();
+    let var = &mut file
+        .add_variable::<u8, _>("one_unlim", &["x", "z"])
+        .unwrap();
     var.put_values(&[0u8, 1, 2, 3], ..).unwrap_err();
     var.put_values(&[0u8, 1, 2, 3], (..2, ..2)).unwrap();
     check_equal(var, &[0u8, 1, 2, 3]);
@@ -1014,7 +1022,7 @@ fn unlimited_dimension_multi_putting() {
     check_equal(var, &[0u8, 1, 2, 3, 4, 5, 6, 7]);
 
     let var = &mut file
-        .add_variable::<u8>("unlim_first", &["z", "x2"])
+        .add_variable::<u8, _>("unlim_first", &["z", "x2"])
         .unwrap();
     var.put_values(&[0u8, 1, 2, 3], (.., ..2)).unwrap();
     check_equal(var, &[0u8, 1, 2, 3]);
@@ -1022,7 +1030,9 @@ fn unlimited_dimension_multi_putting() {
         .unwrap();
     check_equal(var, &[0u8, 1, 2, 3, 4, 5, 6, 7]);
 
-    let var = &mut file.add_variable::<u8>("two_unlim", &["x3", "x4"]).unwrap();
+    let var = &mut file
+        .add_variable::<u8, _>("two_unlim", &["x3", "x4"])
+        .unwrap();
     var.set_fill_value(0u8).unwrap();
     var.put_values(&[0u8, 1, 2, 3], (.., ..)).unwrap_err();
     var.put_values(&[0u8, 1, 2, 3], [..1, ..4]).unwrap();
@@ -1046,10 +1056,10 @@ fn length_of_variable() {
     file.add_dimension("y", 6).unwrap();
     file.add_unlimited_dimension("z").unwrap();
 
-    let var = &mut file.add_variable::<f32>("x", &["x", "y"]).unwrap();
+    let var = &mut file.add_variable::<f32, _>("x", &["x", "y"]).unwrap();
     assert_eq!(var.len(), 4 * 6);
 
-    let var = &mut file.add_variable::<f64>("z", &["x", "z"]).unwrap();
+    let var = &mut file.add_variable::<f64, _>("z", &["x", "z"]).unwrap();
     var.put_value(1u8, [2, 8]).unwrap();
     assert_eq!(var.len(), 4 * 9);
 }
@@ -1060,7 +1070,7 @@ fn single_length_variable() {
     let path = d.path().join("single_length_variable.nc");
     let mut file = netcdf::create(&path).unwrap();
 
-    let var = &mut file.add_variable::<u8>("x", &[]).unwrap();
+    let var = &mut file.add_variable::<u8, _>("x", ()).unwrap();
 
     var.put_value(3u8, ..).unwrap();
     assert_eq!(var.get_value::<u8, _>(..).unwrap(), 3_u8);
@@ -1098,10 +1108,10 @@ fn put_then_def() {
     let path = d.path().join("put_then_def.nc");
     let mut file = netcdf::create(path).unwrap();
 
-    let var = &mut file.add_variable::<i8>("x", &[]).unwrap();
+    let var = &mut file.add_variable::<i8, _>("x", ()).unwrap();
     var.put_value(3i8, ..).unwrap();
 
-    let var2 = &mut file.add_variable::<i8>("y", &[]).unwrap();
+    let var2 = &mut file.add_variable::<i8, _>("y", ()).unwrap();
     var2.put_value(4i8, ..).unwrap();
 }
 
@@ -1126,7 +1136,7 @@ fn string_variables() {
         // Some weird interaction between unlimited dimensions, put_str,
         // and the name of this variable leads to crash. This
         // can be observed by changing this     \ /    to "x"
-        let var = &mut file.add_variable::<i32>("y", &[]).unwrap();
+        let var = &mut file.add_variable::<i32, &[&str]>("y", &[]).unwrap();
         var.put_value(42i32, ()).unwrap();
     }
     let file = netcdf::open(path).unwrap();
@@ -1164,8 +1174,8 @@ fn unlimited_in_parents() {
     let mut file = netcdf::append(&path).unwrap();
 
     let g = &mut file.group_mut("g").unwrap().unwrap();
-    g.add_variable::<i16>("w", &["z1"]).unwrap();
-    g.add_variable::<u16>("v", &["x"]).unwrap();
+    g.add_variable::<i16, _>("w", &["z1"]).unwrap();
+    g.add_variable::<u16, _>("v", &["x"]).unwrap();
 }
 
 #[test]
@@ -1189,21 +1199,15 @@ fn dimension_identifiers() {
         };
 
         // Create variables
-        file.add_variable_from_identifiers::<i8>("v_self_id", &[vrootid])
-            .unwrap();
+        file.add_variable::<i8, _>("v_self_id", &[vrootid]).unwrap();
         let mut g = file.group_mut("g").unwrap().unwrap();
-        g.add_variable_from_identifiers::<i8>("v_root_id", &[vrootid])
-            .unwrap();
-        g.add_variable_from_identifiers::<i8>("v_self_id", &[vgid])
-            .unwrap();
+        g.add_variable::<i8, _>("v_root_id", &[vrootid]).unwrap();
+        g.add_variable::<i8, _>("v_self_id", &[vgid]).unwrap();
 
         let gg = &mut g.group_mut("g").unwrap();
-        gg.add_variable_from_identifiers::<i8>("v_root_id", &[vrootid])
-            .unwrap();
-        gg.add_variable_from_identifiers::<i8>("v_up_id", &[vgid])
-            .unwrap();
-        gg.add_variable_from_identifiers::<i8>("v_self_id", &[vggid])
-            .unwrap();
+        gg.add_variable::<i8, _>("v_root_id", &[vrootid]).unwrap();
+        gg.add_variable::<i8, _>("v_up_id", &[vgid]).unwrap();
+        gg.add_variable::<i8, _>("v_self_id", &[vggid]).unwrap();
     }
 
     let file = &netcdf::open(path).unwrap();
@@ -1276,7 +1280,7 @@ fn set_get_endian() {
             let mut file_w = netcdf::create(&f).unwrap();
             file_w.add_dimension(dim_name, 3).unwrap();
             let var = &mut file_w
-                .add_variable::<i32>("some_variable", &[dim_name])
+                .add_variable::<i32, _>("some_variable", &[dim_name])
                 .unwrap();
             var.set_endianness(*i).unwrap();
             assert_eq!(var.endianness().unwrap(), *i);
@@ -1304,7 +1308,9 @@ mod strided {
             file.add_dimension("z", 3).unwrap();
             file.add_dimension("y", 5).unwrap();
             file.add_dimension("x", 9).unwrap();
-            let mut var = file.add_variable::<i32>("data", &["z", "y", "x"]).unwrap();
+            let mut var = file
+                .add_variable::<i32, _>("data", &["z", "y", "x"])
+                .unwrap();
             let buffer = (0..3 * 5 * 9).collect::<Vec<_>>();
             var.put_values(&buffer, ..).unwrap();
         }
@@ -1427,7 +1433,7 @@ mod strided {
         file.add_dimension("d0", 7).unwrap();
         file.add_dimension("d1", 9).unwrap();
 
-        let mut var = file.add_variable::<i32>("var", &["d0", "d1"]).unwrap();
+        let mut var = file.add_variable::<i32, _>("var", &["d0", "d1"]).unwrap();
 
         let values = (0..7 * 9).collect::<Vec<i32>>();
         let zeros = [0; 7 * 9];
@@ -1506,7 +1512,7 @@ fn dimension_identifiers_from_different_ncids() {
     let d1 = g1.add_dimension("d", 11).unwrap();
 
     match file0
-        .add_variable_from_identifiers::<u8>("var", &[d1.identifier()])
+        .add_variable::<u8, _>("var", &[d1.identifier()])
         .unwrap_err()
     {
         netcdf::Error::WrongDataset => (),
@@ -1515,16 +1521,13 @@ fn dimension_identifiers_from_different_ncids() {
 
     let d0 = file0.add_dimension("f", 20).unwrap();
     let id = d0.identifier();
-    file0
-        .add_variable_from_identifiers::<u8>("var2", &[id])
-        .unwrap();
+    file0.add_variable::<u8, _>("var2", &[id]).unwrap();
 
     let mut g0 = file0.add_group("g").unwrap();
     let dg = g0.add_dimension("h", 17).unwrap();
     let idg = dg.identifier();
 
-    g0.add_variable_from_identifiers::<u8>("var3", &[id, idg])
-        .unwrap();
+    g0.add_variable::<u8, _>("var3", &[id, idg]).unwrap();
 }
 
 #[test]
@@ -1550,7 +1553,7 @@ fn open_to_find_unlim_dim() {
         let mut file = netcdf::create(&path).unwrap();
         file.add_unlimited_dimension("a").unwrap();
 
-        let mut var = file.add_variable::<i32>("b", &["a"]).unwrap();
+        let mut var = file.add_variable::<i32, _>("b", &["a"]).unwrap();
         var.put_values(&[0, 1, 2, 3, 4, 5], ..).unwrap();
     }
 
@@ -1600,7 +1603,7 @@ fn drop_dim_on_simple_indices() {
     f.add_dimension("d4", 13).unwrap();
 
     let var = f
-        .add_variable::<i8>("v", &["d1", "d2", "d3", "d4"])
+        .add_variable::<i8, _>("v", &["d1", "d2", "d3", "d4"])
         .unwrap();
 
     let values = var.get::<i8, _>(..).unwrap();
@@ -1639,7 +1642,7 @@ fn ndarray_put() {
     let values = ndarray::Array::<u8, _>::zeros((10, 11, 12, 13));
 
     let mut var = f
-        .add_variable::<u8>("var", &["d1", "d2", "d3", "d4"])
+        .add_variable::<u8, _>("var", &["d1", "d2", "d3", "d4"])
         .unwrap();
 
     macro_rules! put_values {
@@ -1672,7 +1675,9 @@ fn ndarray_put() {
     put_values!(var, (.., .., .., 1), values, s![.., .., .., ..], Failure);
 
     std::mem::drop(var);
-    let mut var = f.add_variable::<u8>("grow", &["grow", "d2", "d3"]).unwrap();
+    let mut var = f
+        .add_variable::<u8, _>("grow", &["grow", "d2", "d3"])
+        .unwrap();
 
     // let values = ndarray::Array::<u8, _>::zeros((10, 11, 12, 13));
     put_values!(var, .., values, s![.., .., .., ..], Failure);
@@ -1715,7 +1720,9 @@ fn ndarray_get_into() {
         (100 * k + 10 * j + i).try_into().unwrap()
     });
 
-    let mut var = f.add_variable::<u64>("var", &["d1", "d2", "d3"]).unwrap();
+    let mut var = f
+        .add_variable::<u64, _>("var", &["d1", "d2", "d3"])
+        .unwrap();
 
     var.put(.., values.view()).unwrap();
 
diff --git a/netcdf/tests/types.rs b/netcdf/tests/types.rs
index efe13a1..2212358 100644
--- a/netcdf/tests/types.rs
+++ b/netcdf/tests/types.rs
@@ -6,16 +6,16 @@ fn test_roundtrip_types() {
     let path = d.path().join("test_roundtrip_types.nc");
     {
         let mut file = netcdf::create(&path).unwrap();
-        file.add_variable::<i8>("i8", &[]).unwrap();
-        file.add_variable::<u8>("u8", &[]).unwrap();
-        file.add_variable::<i16>("i16", &[]).unwrap();
-        file.add_variable::<u16>("u16", &[]).unwrap();
-        file.add_variable::<i32>("i32", &[]).unwrap();
-        file.add_variable::<u32>("u32", &[]).unwrap();
-        file.add_variable::<i64>("i64", &[]).unwrap();
-        file.add_variable::<u64>("u64", &[]).unwrap();
-        file.add_variable::<f32>("f32", &[]).unwrap();
-        file.add_variable::<f64>("f64", &[]).unwrap();
+        file.add_variable::<i8, _>("i8", ()).unwrap();
+        file.add_variable::<u8, _>("u8", ()).unwrap();
+        file.add_variable::<i16, _>("i16", ()).unwrap();
+        file.add_variable::<u16, _>("u16", ()).unwrap();
+        file.add_variable::<i32, _>("i32", ()).unwrap();
+        file.add_variable::<u32, _>("u32", ()).unwrap();
+        file.add_variable::<i64, _>("i64", ()).unwrap();
+        file.add_variable::<u64, _>("u64", ()).unwrap();
+        file.add_variable::<f32, _>("f32", ()).unwrap();
+        file.add_variable::<f64, _>("f64", ()).unwrap();
         file.add_string_variable("string", &[]).unwrap();
     }