diff --git a/rust/examples/dhtnode.rs b/rust/examples/dhtnode.rs
index 440d52f3cf256a29024c740f0e698fafef94be3e..191f17a1b3aa1127c4103f84a9ee76872250af18 100644
--- a/rust/examples/dhtnode.rs
+++ b/rust/examples/dhtnode.rs
@@ -1,5 +1,23 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
 extern crate opendht;
-use std::ffi::CString;
+// TODO remove dead code warning
 use std::{thread, time};
 use libc::c_void;
 
diff --git a/rust/src/blob.rs b/rust/src/blob.rs
new file mode 100644
index 0000000000000000000000000000000000000000..292549b5944e1d8a691709bac913340e7d533bd0
--- /dev/null
+++ b/rust/src/blob.rs
@@ -0,0 +1,37 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use crate::ffi::*;
+
+pub use crate::ffi::Blob;
+
+impl Blob {
+    pub fn data(&self) -> DataView {
+        unsafe {
+            dht_blob_get_data(self)
+        }
+    }
+}
+
+impl Drop for Blob {
+    fn drop(&mut self) {
+        unsafe {
+            dht_blob_delete(&mut *self)
+        }
+    }
+}
\ No newline at end of file
diff --git a/rust/src/crypto.rs b/rust/src/crypto.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e873d4e125b7f86cb9865f6d07743405f15e34b5
--- /dev/null
+++ b/rust/src/crypto.rs
@@ -0,0 +1,112 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use crate::ffi::*;
+use std::ffi::CString;
+
+impl PublicKey {
+    pub fn new() -> Box<PublicKey> {
+        unsafe {
+            Box::from_raw(dht_publickey_new())
+        }
+    }
+
+    pub fn unpack(&mut self, data: Vec<u8>) -> i32 {
+        unsafe {
+            dht_publickey_unpack(&mut *self, data.as_ptr(), data.len())
+        }
+    }
+
+    pub fn pack(&mut self, data: &str) -> i32 {
+        let data = CString::new(data).unwrap();
+        unsafe {
+            dht_publickey_pack(&mut *self,
+                data.as_ptr(),
+                data.as_bytes().len())
+        }
+    }
+
+    pub fn id(&self) -> InfoHash {
+        unsafe {
+            dht_publickey_get_id(self)
+        }
+    }
+
+    pub fn long_id(&self) -> PkId {
+        unsafe {
+            dht_publickey_get_long_id(self)
+        }
+    }
+
+    pub fn check_signature(&self, data: &str, signature: &str) -> bool {
+        let data = CString::new(data).unwrap();
+        let signature = CString::new(signature).unwrap();
+        unsafe {
+            dht_publickey_check_signature(self,
+                data.as_ptr(), data.as_bytes().len(),
+                signature.as_ptr(), signature.as_bytes().len())
+        }
+    }
+
+    pub fn encrypt(&self, data: &str) -> Box<Blob> {
+        let data = CString::new(data).unwrap();
+        unsafe {
+            Box::from_raw(dht_publickey_encrypt(self,
+                data.as_ptr(), data.as_bytes().len()))
+        }
+    }
+}
+
+impl Drop for PublicKey {
+    fn drop(&mut self) {
+        unsafe {
+            dht_publickey_delete(&mut *self)
+        }
+    }
+}
+
+impl PrivateKey {
+    pub fn new(key_length_bits: u32) -> Box<PrivateKey> {
+        unsafe {
+            Box::from_raw(dht_privatekey_generate(key_length_bits))
+        }
+    }
+
+    pub fn import(data: &str, password: &str) -> Box<PrivateKey> {
+        let password = CString::new(password).unwrap();
+        unsafe {
+            Box::from_raw(dht_privatekey_import(data.as_ptr(),
+                data.as_bytes().len(), password.as_ptr()))
+        }
+    }
+
+    pub fn public_key(&self) -> Box<PublicKey> {
+        unsafe {
+            Box::from_raw(dht_privatekey_get_publickey(self))
+        }
+    }
+
+}
+
+impl Drop for PrivateKey {
+    fn drop(&mut self) {
+        unsafe {
+            dht_privatekey_delete(&mut *self)
+        }
+    }
+}
\ No newline at end of file
diff --git a/rust/src/dhtrunner.rs b/rust/src/dhtrunner.rs
new file mode 100644
index 0000000000000000000000000000000000000000..61738ce2346d2364538bc4934704d4f661bba7e5
--- /dev/null
+++ b/rust/src/dhtrunner.rs
@@ -0,0 +1,105 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use crate::ffi::*;
+use libc::c_void;
+use std::ffi::CString;
+use std::fmt;
+
+pub use crate::ffi::{ DhtRunner, OpToken, Value };
+
+impl DhtRunner {
+    pub fn new() -> Box<DhtRunner> {
+        unsafe {
+            Box::from_raw(dht_runner_new())
+        }
+    }
+
+    pub fn run(&mut self, port: u16) {
+        unsafe {
+            dht_runner_run(&mut *self, port)
+        }
+    }
+
+    pub fn bootstrap(&mut self, host: &str, service: u16) {
+        unsafe {
+            dht_runner_bootstrap(&mut *self,
+                CString::new(host).unwrap().as_ptr(),
+                CString::new(service.to_string()).unwrap().as_ptr())
+        }
+    }
+
+    pub fn get(&mut self, h: &InfoHash,
+               get_cb: extern fn(*mut Value, *mut c_void),
+               done_cb: extern fn(bool, *mut c_void),
+               cb_user_data: *mut c_void) {
+
+        unsafe {
+            dht_runner_get(&mut *self, h, get_cb, done_cb, cb_user_data)
+        }
+    }
+
+    pub fn put(&mut self, h: &InfoHash, v: Box<Value>,
+               done_cb: extern fn(bool, *mut c_void),
+               cb_user_data: *mut c_void) {
+
+        unsafe {
+            dht_runner_put(&mut *self, h, &*v, done_cb, cb_user_data)
+        }
+    }
+
+    pub fn listen(&mut self, h: &InfoHash,
+                  cb: extern fn(*mut Value, bool, *mut c_void),
+                  cb_user_data: *mut c_void) -> Box<OpToken> {
+        unsafe {
+            Box::from_raw(dht_runner_listen(&mut *self, h, cb, cb_user_data))
+        }
+    }
+
+    pub fn cancel_listen(&mut self, h: &InfoHash, token: Box<OpToken>) {
+
+        unsafe {
+            dht_runner_cancel_listen(&mut *self, h, &*token)
+        }
+    }
+
+    pub fn shutdown(&mut self,
+                    done_cb: extern fn(bool, *mut c_void),
+                    cb_user_data: *mut c_void)
+    {
+        unsafe {
+            dht_runner_shutdown(&mut *self, done_cb, cb_user_data)
+        }
+    }
+}
+
+impl Drop for DhtRunner {
+    fn drop(&mut self) {
+        unsafe {
+            dht_runner_delete(&mut *self)
+        }
+    }
+}
+
+impl Drop for OpToken {
+    fn drop(&mut self) {
+        unsafe {
+            dht_op_token_delete(&mut *self)
+        }
+    }
+}
\ No newline at end of file
diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs
index a4ea6da0400f0f812472446ea62dc5df8ee7e38c..4f9522ef4fdb39b16c84f82663d19f707476e1cc 100644
--- a/rust/src/ffi.rs
+++ b/rust/src/ffi.rs
@@ -1,3 +1,21 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
 use libc::{c_char, c_int, c_uint, c_void, in_port_t, size_t};
 
 const HASH_LEN: usize = 20;
diff --git a/rust/src/infohash.rs b/rust/src/infohash.rs
new file mode 100644
index 0000000000000000000000000000000000000000..31966a212b0a3461bc269ef177c2e2de4b0414ff
--- /dev/null
+++ b/rust/src/infohash.rs
@@ -0,0 +1,66 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use crate::ffi::*;
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::fmt;
+
+pub use crate::ffi::InfoHash;
+
+impl InfoHash {
+    pub fn new() -> InfoHash {
+        InfoHash {
+            d: [0; 20]
+        }
+    }
+
+    pub fn random() -> InfoHash {
+        let mut h = InfoHash::new();
+        unsafe {
+            dht_infohash_random(&mut h);
+        }
+        h
+    }
+
+    pub fn get(data: &str) -> InfoHash {
+        let mut h = InfoHash::new();
+        unsafe {
+            let c_str = CString::new(data).unwrap();
+            dht_infohash_get(&mut h, c_str.as_ptr() as *mut u8, data.len());
+        }
+        h
+    }
+
+    pub fn is_zero(&self) -> bool {
+        unsafe {
+            dht_infohash_is_zero(self)
+        }
+    }
+}
+
+impl fmt::Display for InfoHash {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        unsafe {
+            let self_str = CStr::from_ptr(
+                    dht_infohash_print(self)
+                ).to_str().unwrap_or("");
+            write!(f, "{}", self_str)
+        }
+    }
+}
\ No newline at end of file
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 6892fe32f8caec64b5c31950f1ee0480878b3aed..7d8db4a0d70fd685c8947bf8a024e459fc8ccee2 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -1,286 +1,33 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
 extern crate libc;
 
+mod blob;
+pub mod crypto;
+mod dhtrunner;
 mod ffi;
-use ffi::*;
-pub use ffi::{ DhtRunner, InfoHash, Value };
-
-use std::fmt;
-use std::ffi::CStr;
-use std::ffi::CString;
-use std::str;
-use std::slice;
-use libc::c_void;
-
-// TODO separate into files
-
-impl InfoHash {
-    pub fn new() -> InfoHash {
-        InfoHash {
-            d: [0; 20]
-        }
-    }
-
-    pub fn random() -> InfoHash {
-        let mut h = InfoHash::new();
-        unsafe {
-            dht_infohash_random(&mut h);
-        }
-        h
-    }
-
-    pub fn get(data: &str) -> InfoHash {
-        let mut h = InfoHash::new();
-        unsafe {
-            let c_str = CString::new(data).unwrap();
-            dht_infohash_get(&mut h, c_str.as_ptr() as *mut u8, data.len());
-        }
-        h
-    }
-
-    pub fn is_zero(&self) -> bool {
-        unsafe {
-            dht_infohash_is_zero(self)
-        }
-    }
-}
-
-impl fmt::Display for InfoHash {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        unsafe {
-            let self_str = CStr::from_ptr(
-                    dht_infohash_print(self)
-                ).to_str().unwrap_or("");
-            write!(f, "{}", self_str)
-        }
-    }
-}
-
-impl Blob {
-    pub fn data(&self) -> DataView {
-        unsafe {
-            dht_blob_get_data(self)
-        }
-    }
-}
-
-impl Drop for Blob {
-    fn drop(&mut self) {
-        unsafe {
-            dht_blob_delete(&mut *self)
-        }
-    }
-}
-
-impl fmt::Display for PkId {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        unsafe {
-            let self_str = CStr::from_ptr(
-                    dht_pkid_print(self)
-                ).to_str().unwrap_or("");
-            write!(f, "{}", self_str)
-        }
-    }
-}
-
-
-impl DhtRunner {
-    pub fn new() -> Box<DhtRunner> {
-        unsafe {
-            Box::from_raw(dht_runner_new())
-        }
-    }
-
-    pub fn run(&mut self, port: u16) {
-        unsafe {
-            dht_runner_run(&mut *self, port)
-        }
-    }
-
-    pub fn bootstrap(&mut self, host: &str, service: u16) {
-        unsafe {
-            dht_runner_bootstrap(&mut *self,
-                CString::new(host).unwrap().as_ptr(),
-                CString::new(service.to_string()).unwrap().as_ptr())
-        }
-    }
-
-    pub fn get(&mut self, h: &InfoHash,
-               get_cb: extern fn(*mut Value, *mut c_void),
-               done_cb: extern fn(bool, *mut c_void),
-               cb_user_data: *mut c_void) {
-
-        unsafe {
-            dht_runner_get(&mut *self, h, get_cb, done_cb, cb_user_data)
-        }
-    }
-
-    pub fn put(&mut self, h: &InfoHash, v: Box<Value>,
-               done_cb: extern fn(bool, *mut c_void),
-               cb_user_data: *mut c_void) {
-
-        unsafe {
-            dht_runner_put(&mut *self, h, &*v, done_cb, cb_user_data)
-        }
-    }
-
-    pub fn listen(&mut self, h: &InfoHash,
-                  cb: extern fn(*mut Value, bool, *mut c_void),
-                  cb_user_data: *mut c_void) -> Box<OpToken> {
-        unsafe {
-            Box::from_raw(dht_runner_listen(&mut *self, h, cb, cb_user_data))
-        }
-    }
-
-    pub fn cancel_listen(&mut self, h: &InfoHash, token: Box<OpToken>) {
-
-        unsafe {
-            dht_runner_cancel_listen(&mut *self, h, &*token)
-        }
-    }
-
-    pub fn shutdown(&mut self,
-                    done_cb: extern fn(bool, *mut c_void),
-                    cb_user_data: *mut c_void)
-    {
-        unsafe {
-            dht_runner_shutdown(&mut *self, done_cb, cb_user_data)
-        }
-    }
-}
-
-impl Drop for DhtRunner {
-    fn drop(&mut self) {
-        unsafe {
-            dht_runner_delete(&mut *self)
-        }
-    }
-}
-
-impl Value {
-    pub fn new(data: &str) -> Box<Value> {
-        unsafe {
-            Box::from_raw(dht_value_new(data.as_bytes().as_ptr(),
-                data.as_bytes().len()))
-        }
-    }
-
-    fn dataview(&self) -> DataView {
-        unsafe {
-            dht_value_get_data(self)
-        }
-    }
-}
-
-impl Drop for Value {
-    fn drop(&mut self) {
-        unsafe {
-            dht_value_unref(&mut *self)
-        }
-    }
-}
-
-impl fmt::Display for Value {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        unsafe {
-            let dataview = self.dataview();
-            let slice = slice::from_raw_parts(dataview.data, dataview.size);
-            write!(f, "{}", str::from_utf8(slice).unwrap_or(""))
-        }
-    }
-}
-
-impl Drop for OpToken {
-    fn drop(&mut self) {
-        unsafe {
-            dht_op_token_delete(&mut *self)
-        }
-    }
-}
-
-impl PublicKey {
-    pub fn new() -> Box<PublicKey> {
-        unsafe {
-            Box::from_raw(dht_publickey_new())
-        }
-    }
-
-    pub fn unpack(&mut self, data: Vec<u8>) -> i32 {
-        unsafe {
-            dht_publickey_unpack(&mut *self, data.as_ptr(), data.len())
-        }
-    }
-
-    // TODO slice instead of CString
-    pub fn pack(&mut self, data: &CString) -> i32 {
-        unsafe {
-            dht_publickey_pack(&mut *self,
-                data.as_ptr(),
-                data.as_bytes().len())
-        }
-    }
-
-    pub fn id(&self) -> InfoHash {
-        unsafe {
-            dht_publickey_get_id(self)
-        }
-    }
-
-    pub fn long_id(&self) -> PkId {
-        unsafe {
-            dht_publickey_get_long_id(self)
-        }
-    }
-
-    pub fn check_signature(&self, data: &CString, signature: &CString) -> bool {
-        unsafe {
-            dht_publickey_check_signature(self,
-                data.as_ptr(), data.as_bytes().len(),
-                signature.as_ptr(), signature.as_bytes().len())
-        }
-    }
-
-    pub fn encrypt(&self, data: &CString) -> Box<Blob> {
-        unsafe {
-            Box::from_raw(dht_publickey_encrypt(self,
-                data.as_ptr(), data.as_bytes().len()))
-        }
-    }
-}
-
-impl Drop for PublicKey {
-    fn drop(&mut self) {
-        unsafe {
-            dht_publickey_delete(&mut *self)
-        }
-    }
-}
-
-impl PrivateKey {
-    pub fn new(key_length_bits: u32) -> Box<PrivateKey> {
-        unsafe {
-            Box::from_raw(dht_privatekey_generate(key_length_bits))
-        }
-    }
-
-    pub fn import(data: &str, password: &CString) -> Box<PrivateKey> {
-        unsafe {
-            Box::from_raw(dht_privatekey_import(data.as_ptr(),
-                data.as_bytes().len(), password.as_ptr()))
-        }
-    }
-
-    pub fn public_key(&self) -> Box<PublicKey> {
-        unsafe {
-            Box::from_raw(dht_privatekey_get_publickey(self))
-        }
-    }
-
-}
-
-impl Drop for PrivateKey {
-    fn drop(&mut self) {
-        unsafe {
-            dht_privatekey_delete(&mut *self)
-        }
-    }
-}
\ No newline at end of file
+mod infohash;
+mod pkid;
+mod value;
+
+pub use blob::Blob;
+pub use dhtrunner::{ DhtRunner, OpToken };
+pub use infohash::InfoHash;
+pub use pkid::PkId;
+pub use value::{ DataView, Value };
diff --git a/rust/src/pkid.rs b/rust/src/pkid.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ded558ac260110e2dd544fb3f7e9b649949dbd7d
--- /dev/null
+++ b/rust/src/pkid.rs
@@ -0,0 +1,34 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use crate::ffi::*;
+use std::ffi::CStr;
+use std::fmt;
+
+pub use crate::ffi::PkId;
+
+impl fmt::Display for PkId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        unsafe {
+            let self_str = CStr::from_ptr(
+                    dht_pkid_print(self)
+                ).to_str().unwrap_or("");
+            write!(f, "{}", self_str)
+        }
+    }
+}
\ No newline at end of file
diff --git a/rust/src/value.rs b/rust/src/value.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b1dd01b820c7cdb8522f70d4a5b5b4118241bd65
--- /dev/null
+++ b/rust/src/value.rs
@@ -0,0 +1,57 @@
+/*
+ *  Copyright (C) 2019 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use crate::ffi::*;
+use std::fmt;
+use std::str;
+use std::slice;
+
+pub use crate::ffi::{DataView, Value};
+
+impl Value {
+    pub fn new(data: &str) -> Box<Value> {
+        unsafe {
+            Box::from_raw(dht_value_new(data.as_bytes().as_ptr(),
+                data.as_bytes().len()))
+        }
+    }
+
+    fn dataview(&self) -> DataView {
+        unsafe {
+            dht_value_get_data(self)
+        }
+    }
+}
+
+impl Drop for Value {
+    fn drop(&mut self) {
+        unsafe {
+            dht_value_unref(&mut *self)
+        }
+    }
+}
+
+impl fmt::Display for Value {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        unsafe {
+            let dataview = self.dataview();
+            let slice = slice::from_raw_parts(dataview.data, dataview.size);
+            write!(f, "{}", str::from_utf8(slice).unwrap_or(""))
+        }
+    }
+}