diff --git a/src/legacy.rs b/src/legacy.rs
index d55f3a1..aad2fd7 100644
--- a/src/legacy.rs
+++ b/src/legacy.rs
@@ -1,8 +1,10 @@
+use crate::Config;
 use core::char;
 use core::fmt;
+use core::fmt::Display;
 
 /// Representation of a demangled symbol name.
-pub struct Demangle<'a> {
+pub(crate) struct Demangle<'a> {
     inner: &'a str,
     /// The number of ::-separated elements in the original name.
     elements: usize,
@@ -46,7 +48,7 @@ pub struct Demangle<'a> {
 // Note that this demangler isn't quite as fancy as it could be. We have lots
 // of other information in our symbols like hashes, version, type information,
 // etc. Additionally, this doesn't handle glue symbols at all.
-pub fn demangle(s: &str) -> Result<(Demangle, &str), ()> {
+pub(crate) fn demangle(s: &str) -> Result<(Demangle, &str), ()> {
     // First validate the symbol. If it doesn't look like anything we're
     // expecting, we just print it literally. Note that we must handle non-Rust
     // symbols because we could have any function in the backtrace.
@@ -102,8 +104,8 @@ fn is_rust_hash(s: &str) -> bool {
     s.starts_with('h') && s[1..].chars().all(|c| c.is_digit(16))
 }
 
-impl<'a> fmt::Display for Demangle<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+impl<'a> Demangle<'a> {
+    pub(crate) fn fmt_with_config(&self, f: &mut fmt::Formatter, config: &Config) -> fmt::Result {
         // Alright, let's do this.
         let mut inner = self.inner;
         for element in 0..self.elements {
@@ -114,9 +116,8 @@ impl<'a> fmt::Display for Demangle<'a> {
             let i: usize = inner[..(inner.len() - rest.len())].parse().unwrap();
             inner = &rest[i..];
             rest = &rest[..i];
-            // Skip printing the hash if alternate formatting
-            // was requested.
-            if f.alternate() && element + 1 == self.elements && is_rust_hash(&rest) {
+            // Skip printing the hash if requested.
+            if !config.with_hash && element + 1 == self.elements && is_rust_hash(&rest) {
                 break;
             }
             if element != 0 {
@@ -208,7 +209,13 @@ mod tests {
 
     macro_rules! t_nohash {
         ($a:expr, $b:expr) => {{
-            assert_eq!(format!("{:#}", ::demangle($a)), $b);
+            assert_eq!(
+                format!(
+                    "{}",
+                    crate::demangle_with_config($a, crate::Config::new().with_hash(false))
+                ),
+                $b
+            );
         }};
     }
 
diff --git a/src/lib.rs b/src/lib.rs
index 2b8684e..589f212 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,7 +12,7 @@
 //! # Examples
 //!
 //! ```
-//! use rustc_demangle::demangle;
+//! use rustc_demangle::{demangle, Config, demangle_with_config};
 //!
 //! assert_eq!(demangle("_ZN4testE").to_string(), "test");
 //! assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar");
@@ -21,6 +21,8 @@
 //! assert_eq!(format!("{}", demangle("_ZN3foo17h05af221e174051e9E")), "foo::h05af221e174051e9");
 //! // Without hash
 //! assert_eq!(format!("{:#}", demangle("_ZN3foo17h05af221e174051e9E")), "foo");
+//! // Without hash by clearing Config::with_hash
+//! assert_eq!(format!("{}", demangle_with_config("_ZN3foo17h05af221e174051e9E", Config::new().with_hash(false))), "foo");
 //! ```
 
 #![no_std]
@@ -35,9 +37,36 @@ mod v0;
 
 use core::fmt;
 
+/// demangle configuration
+#[derive(Clone, Debug)]
+pub struct Config {
+    with_hash: bool,
+    // extend with more config options in the future
+}
+
+impl Default for Config {
+    fn default() -> Self {
+        Self { with_hash: true }
+    }
+}
+
+impl Config {
+    /// create the default demangle configuration
+    pub fn new() -> Self {
+        Self::default()
+    }
+    /// set if disambiguating hashes should be displayed. This is the default.
+    ///
+    /// using `format!("{:#}", ...)` is the alternate method of disabling `with_hash`.
+    pub fn with_hash(self, with_hash: bool) -> Self {
+        Self { with_hash, ..self }
+    }
+}
+
 /// Representation of a demangled symbol name.
 pub struct Demangle<'a> {
     style: Option<DemangleStyle<'a>>,
+    config: Config,
     original: &'a str,
     suffix: &'a str,
 }
@@ -62,7 +91,26 @@ enum DemangleStyle<'a> {
 /// assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar");
 /// assert_eq!(demangle("foo").to_string(), "foo");
 /// ```
-pub fn demangle(mut s: &str) -> Demangle {
+pub fn demangle(s: &str) -> Demangle {
+    demangle_with_config(s, Config::new())
+}
+
+/// De-mangles a Rust symbol into a more readable version
+///
+/// This function will take a **mangled** symbol and return a value. When printed,
+/// the de-mangled version will be written. If the symbol does not look like
+/// a mangled symbol, the original value will be written instead.
+///
+/// # Examples
+///
+/// ```
+/// use rustc_demangle::demangle;
+///
+/// assert_eq!(demangle("_ZN4testE").to_string(), "test");
+/// assert_eq!(demangle("_ZN3foo3barE").to_string(), "foo::bar");
+/// assert_eq!(demangle("foo").to_string(), "foo");
+/// ```
+pub fn demangle_with_config(mut s: &str, config: Config) -> Demangle {
     // During ThinLTO LLVM may import and rename internal symbols, so strip out
     // those endings first as they're one of the last manglings applied to symbol
     // names.
@@ -108,6 +156,7 @@ pub fn demangle(mut s: &str) -> Demangle {
 
     Demangle {
         style,
+        config,
         original: s,
         suffix,
     }
@@ -134,7 +183,25 @@ pub struct TryDemangleError {
 /// assert_eq!(rustc_demangle::demangle(not_a_rust_symbol).as_str(), not_a_rust_symbol);
 /// ```
 pub fn try_demangle(s: &str) -> Result<Demangle, TryDemangleError> {
-    let sym = demangle(s);
+    try_demangle_with_config(s, Config::new())
+}
+
+/// The same as `demangle`, except return an `Err` if the string does not appear
+/// to be a Rust symbol, rather than "demangling" the given string as a no-op.
+///
+/// ```
+/// extern crate rustc_demangle;
+///
+/// let not_a_rust_symbol = "la la la";
+///
+/// // The `try_demangle` function will reject strings which are not Rust symbols.
+/// assert!(rustc_demangle::try_demangle(not_a_rust_symbol).is_err());
+///
+/// // While `demangle` will just pass the non-symbol through as a no-op.
+/// assert_eq!(rustc_demangle::demangle(not_a_rust_symbol).as_str(), not_a_rust_symbol);
+/// ```
+pub fn try_demangle_with_config(s: &str, config: Config) -> Result<Demangle, TryDemangleError> {
+    let sym = demangle_with_config(s, config);
     if sym.style.is_some() {
         Ok(sym)
     } else {
@@ -147,6 +214,10 @@ impl<'a> Demangle<'a> {
     pub fn as_str(&self) -> &'a str {
         self.original
     }
+    /// Set the demangling configuration
+    pub fn with_config(self, config: Config) -> Self {
+        Self { config, ..self }
+    }
 }
 
 fn is_symbol_like(s: &str) -> bool {
@@ -178,10 +249,14 @@ fn is_ascii_punctuation(c: char) -> bool {
 
 impl<'a> fmt::Display for Demangle<'a> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let mut config = self.config.clone();
+        if f.alternate() {
+            config = config.with_hash(false);
+        }
         match self.style {
             None => f.write_str(self.original)?,
-            Some(DemangleStyle::Legacy(ref d)) => fmt::Display::fmt(d, f)?,
-            Some(DemangleStyle::V0(ref d)) => fmt::Display::fmt(d, f)?,
+            Some(DemangleStyle::Legacy(ref d)) => d.fmt_with_config(f, &config)?,
+            Some(DemangleStyle::V0(ref d)) => d.fmt_with_config(f, &config)?,
         }
         f.write_str(self.suffix)
     }
@@ -209,12 +284,24 @@ mod tests {
         };
     }
 
-    macro_rules! t_nohash {
+    macro_rules! t_nohash_alt {
         ($a:expr, $b:expr) => {{
             assert_eq!(format!("{:#}", super::demangle($a)), $b);
         }};
     }
 
+    macro_rules! t_nohash {
+        ($a:expr, $b:expr) => {{
+            assert_eq!(
+                format!(
+                    "{}",
+                    super::demangle_with_config($a, super::Config::new().with_hash(false))
+                ),
+                $b
+            );
+        }};
+    }
+
     fn ok(sym: &str, expected: &str) -> bool {
         match super::try_demangle(sym) {
             Ok(s) => {
@@ -304,6 +391,13 @@ mod tests {
         t_nohash!(s, "foo");
     }
 
+    #[test]
+    fn demangle_without_hash_alt() {
+        let s = "_ZN3foo17h05af221e174051e9E";
+        t!(s, "foo::h05af221e174051e9");
+        t_nohash_alt!(s, "foo");
+    }
+
     #[test]
     fn demangle_without_hash_edgecases() {
         // One element, no hash.
diff --git a/src/v0.rs b/src/v0.rs
index c941acd..5bfa08f 100644
--- a/src/v0.rs
+++ b/src/v0.rs
@@ -1,9 +1,10 @@
+use crate::Config;
 use core::char;
 use core::fmt;
 use core::fmt::Display;
 
 /// Representation of a demangled symbol name.
-pub struct Demangle<'a> {
+pub(crate) struct Demangle<'a> {
     inner: &'a str,
 }
 
@@ -12,7 +13,7 @@ pub struct Demangle<'a> {
 /// This function will take a **mangled** symbol and return a value. When printed,
 /// the de-mangled version will be written. If the symbol does not look like
 /// a mangled symbol, the original value will be written instead.
-pub fn demangle(s: &str) -> Result<(Demangle, &str), Invalid> {
+pub(crate) fn demangle(s: &str) -> Result<(Demangle, &str), Invalid> {
     // First validate the symbol. If it doesn't look like anything we're
     // expecting, we just print it literally. Note that we must handle non-Rust
     // symbols because we could have any function in the backtrace.
@@ -56,14 +57,15 @@ pub fn demangle(s: &str) -> Result<(Demangle, &str), Invalid> {
     Ok((Demangle { inner }, &parser.sym[parser.next..]))
 }
 
-impl<'s> Display for Demangle<'s> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+impl<'s> Demangle<'s> {
+    pub(crate) fn fmt_with_config(&self, f: &mut fmt::Formatter, config: &Config) -> fmt::Result {
         let mut printer = Printer {
             parser: Ok(Parser {
                 sym: self.inner,
                 next: 0,
             }),
             out: f,
+            config,
             bound_lifetime_depth: 0,
         };
         printer.print_path(true)
@@ -71,7 +73,7 @@ impl<'s> Display for Demangle<'s> {
 }
 
 #[derive(PartialEq, Eq)]
-pub struct Invalid;
+pub(crate) struct Invalid;
 
 struct Ident<'s> {
     /// ASCII part of the identifier.
@@ -205,8 +207,8 @@ impl<'s> Ident<'s> {
     }
 }
 
-impl<'s> Display for Ident<'s> {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+impl<'s> Ident<'s> {
+    fn fmt_with_config(&self, f: &mut fmt::Formatter, _config: &Config) -> fmt::Result {
         self.try_small_punycode_decode(|chars| {
             for &c in chars {
                 c.fmt(f)?;
@@ -550,6 +552,7 @@ impl<'s> Parser<'s> {
 struct Printer<'a, 'b: 'a, 's> {
     parser: Result<Parser<'s>, Invalid>,
     out: &'a mut fmt::Formatter<'b>,
+    config: &'a Config,
     bound_lifetime_depth: u32,
 }
 
@@ -594,6 +597,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
         Printer {
             parser: self.parser_mut().and_then(|p| p.backref()),
             out: self.out,
+            config: self.config,
             bound_lifetime_depth: self.bound_lifetime_depth,
         }
     }
@@ -676,8 +680,8 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
                 let dis = parse!(self, disambiguator);
                 let name = parse!(self, ident);
 
-                name.fmt(self.out)?;
-                if !self.out.alternate() {
+                name.fmt_with_config(self.out, self.config)?;
+                if self.config.with_hash {
                     self.out.write_str("[")?;
                     fmt::LowerHex::fmt(&dis, self.out)?;
                     self.out.write_str("]")?;
@@ -702,7 +706,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
                         }
                         if !name.ascii.is_empty() || !name.punycode.is_empty() {
                             self.out.write_str(":")?;
-                            name.fmt(self.out)?;
+                            name.fmt_with_config(self.out, self.config)?;
                         }
                         self.out.write_str("#")?;
                         dis.fmt(self.out)?;
@@ -713,7 +717,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
                     None => {
                         if !name.ascii.is_empty() || !name.punycode.is_empty() {
                             self.out.write_str("::")?;
-                            name.fmt(self.out)?;
+                            name.fmt_with_config(self.out, self.config)?;
                         }
                     }
                 }
@@ -918,7 +922,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
             }
 
             let name = parse!(self, ident);
-            name.fmt(self.out)?;
+            name.fmt_with_config(self.out, self.config)?;
             self.out.write_str(" = ")?;
             self.print_type()?;
         }
@@ -949,7 +953,7 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
             self.print_const_uint()?;
         }
 
-        if !self.out.alternate() {
+        if self.config.with_hash {
             self.out.write_str(": ")?;
             self.out.write_str(ty)?;
         }
@@ -978,7 +982,13 @@ impl<'a, 'b, 's> Printer<'a, 'b, 's> {
 mod tests {
     macro_rules! t_nohash {
         ($a:expr, $b:expr) => {{
-            assert_eq!(format!("{:#}", ::demangle($a)), $b);
+            assert_eq!(
+                format!(
+                    "{}",
+                    crate::demangle_with_config($a, crate::Config::new().with_hash(false))
+                ),
+                $b
+            );
         }};
     }
     macro_rules! t_nohash_type {