You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2025/04/18 01:49:03 UTC

(opendal) branch main updated: feat(bindings/cpp) remove lib.rs.h from opendal.hpp (#6041)

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

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 155023f53 feat(bindings/cpp) remove lib.rs.h from opendal.hpp (#6041)
155023f53 is described below

commit 155023f53335cb8461129ce808edd7dc0fc454a7
Author: deadlinefen <de...@bupt.edu.cn>
AuthorDate: Fri Apr 18 09:48:57 2025 +0800

    feat(bindings/cpp) remove lib.rs.h from opendal.hpp (#6041)
    
    * feat(bindings/cpp) remove lib.rs.h from opendal.hpp
    
    * fix(bindings/cpp): fix missing include header in operator.hpp
    
    Signed-off-by: Jack Drogon <ja...@gmail.com>
    
    * fix(example/cpp): fix CMakeLists.txt to use local cpp bindings
    
    Signed-off-by: Jack Drogon <ja...@gmail.com>
    
    * chore(bindings/cpp): fix license with blank line && format code
    
    Signed-off-by: Jack Drogon <ja...@gmail.com>
    
    ---------
    
    Signed-off-by: Jack Drogon <ja...@gmail.com>
    Co-authored-by: Jack Drogon <ja...@gmail.com>
---
 bindings/cpp/CMakeLists.txt           |  10 +-
 bindings/cpp/include/opendal.hpp      |  66 +++++++------
 bindings/cpp/src/details/common.hpp   |  46 +++++++++
 bindings/cpp/src/details/lister.hpp   |  58 +++++++++++
 bindings/cpp/src/details/operator.cpp |  84 ++++++++++++++++
 bindings/cpp/src/details/operator.hpp |  75 +++++++++++++++
 bindings/cpp/src/details/reader.cpp   |  48 ++++++++++
 bindings/cpp/src/details/reader.hpp   |  51 ++++++++++
 bindings/cpp/src/opendal.cpp          | 175 ++++++++++++++++------------------
 bindings/cpp/tests/basic_test.cpp     |  18 ++--
 examples/cpp/CMakeLists.txt           |  10 +-
 examples/cpp/basic.cpp                |  15 ++-
 12 files changed, 513 insertions(+), 143 deletions(-)

diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt
index 2762a1430..e3548f9f5 100644
--- a/bindings/cpp/CMakeLists.txt
+++ b/bindings/cpp/CMakeLists.txt
@@ -97,7 +97,15 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
 else()
     set(RUST_LIB ${CARGO_TARGET_DIR}/release/${CMAKE_STATIC_LIBRARY_PREFIX}opendal_cpp${CMAKE_STATIC_LIBRARY_SUFFIX})
 endif()
-set(CPP_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include ${CARGO_TARGET_DIR}/cxxbridge/opendal-cpp/src)
+
+set(CPP_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include
+                    ${PROJECT_SOURCE_DIR}/src
+                    ${CARGO_TARGET_DIR}/cxxbridge
+                    ${CARGO_TARGET_DIR}/cxxbridge/opendal-cpp/src)
+
+file(GLOB CPP_SOURCE_FILE "src/details/*.cpp")
+file(GLOB CPP_HEADER_FILE "src/details/*.hpp")
+
 list(APPEND CPP_SOURCE_FILE src/opendal.cpp)
 list(APPEND CPP_HEADER_FILE include/opendal.hpp)
 if (OPENDAL_ENABLE_ASYNC)
diff --git a/bindings/cpp/include/opendal.hpp b/bindings/cpp/include/opendal.hpp
index cbf96476d..a5e4b901e 100644
--- a/bindings/cpp/include/opendal.hpp
+++ b/bindings/cpp/include/opendal.hpp
@@ -19,24 +19,29 @@
 
 #pragma once
 
-#include <memory>
+#include <cstdint>
 #include <optional>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
-#include "boost/date_time/posix_time/posix_time.hpp"
+#include "boost/date_time/posix_time/ptime.hpp"
 #include "boost/iostreams/concepts.hpp"
 #include "boost/iostreams/stream.hpp"
-#include "lib.rs.h"
 
 namespace opendal {
 
+namespace details {
+class Operator;
+class Reader;
+class Lister;
+}  // namespace details
+
 /**
  * @enum EntryMode
  * @brief The mode of the entry
  */
-enum EntryMode {
+enum class EntryMode : int {
   FILE = 1,
   DIR = 2,
   UNKNOWN = 0,
@@ -55,8 +60,6 @@ struct Metadata {
   std::optional<std::string> content_type;
   std::optional<std::string> etag;
   std::optional<boost::posix_time::ptime> last_modified;
-
-  Metadata(ffi::Metadata &&);
 };
 
 /**
@@ -65,8 +68,6 @@ struct Metadata {
  */
 struct Entry {
   std::string path;
-
-  Entry(ffi::Entry &&);
 };
 
 class Reader;
@@ -78,7 +79,7 @@ class Lister;
  */
 class Operator {
  public:
-  Operator() = default;
+  Operator();
 
   /**
    * @brief Construct a new Operator object
@@ -94,9 +95,10 @@ class Operator {
   Operator &operator=(const Operator &) = delete;
 
   // Enable move
-  Operator(Operator &&) = default;
-  Operator &operator=(Operator &&) = default;
-  ~Operator() = default;
+  Operator(Operator &&);
+  Operator &operator=(Operator &&);
+
+  ~Operator();
 
   /**
    * @brief Check if the operator is available
@@ -113,7 +115,7 @@ class Operator {
    * @param path The path of the data
    * @return The data read from the operator
    */
-  std::vector<uint8_t> read(std::string_view path);
+  std::string read(std::string_view path);
 
   /**
    * @brief Write data to the operator
@@ -121,7 +123,7 @@ class Operator {
    * @param path The path of the data
    * @param data The data to write
    */
-  void write(std::string_view path, const std::vector<uint8_t> &data);
+  void write(std::string_view path, std::string_view data);
 
   /**
    * @brief Read data from the operator
@@ -198,7 +200,7 @@ class Operator {
   Lister lister(std::string_view path);
 
  private:
-  std::optional<rust::Box<opendal::ffi::Operator>> operator_;
+  std::unique_ptr<details::Operator> operator_;
 };
 
 /**
@@ -213,14 +215,18 @@ class Operator {
 class Reader
     : public boost::iostreams::device<boost::iostreams::input_seekable> {
  public:
-  Reader(rust::Box<opendal::ffi::Reader> &&reader)
-      : raw_reader_(std::move(reader)) {}
+  Reader(std::unique_ptr<details::Reader> &&reader);
+
+  Reader(Reader &&);
+
+  ~Reader();
 
   std::streamsize read(void *s, std::streamsize n);
+
   std::streampos seek(std::streamoff off, std::ios_base::seekdir way);
 
  private:
-  rust::Box<opendal::ffi::Reader> raw_reader_;
+  std::unique_ptr<details::Reader> raw_reader_;
 };
 
 // Boost IOStreams requires it to be copyable. So we need to use
@@ -259,8 +265,11 @@ class ReaderStream
  */
 class Lister {
  public:
-  Lister(rust::Box<opendal::ffi::Lister> &&lister)
-      : raw_lister_(std::move(lister)) {}
+  Lister(std::unique_ptr<details::Lister> lister);
+
+  Lister(Lister &&);
+
+  ~Lister();
 
   /**
    * @class ListerIterator
@@ -268,7 +277,7 @@ class Lister {
    * @note It's an undefined behavior to make multiple iterators from one
    * Lister.
    */
-  class ListerIterator {
+  class Iterator {
    public:
     using iterator_category = std::input_iterator_tag;
     using value_type = Entry;
@@ -276,27 +285,27 @@ class Lister {
     using pointer = Entry *;
     using reference = Entry &;
 
-    ListerIterator(Lister &lister) : lister_(lister) {
+    Iterator(Lister &lister) : lister_{lister} {
       current_entry_ = lister_.next();
     }
 
     Entry operator*() { return current_entry_.value(); }
 
-    ListerIterator &operator++() {
+    Iterator &operator++() {
       if (current_entry_) {
         current_entry_ = lister_.next();
       }
       return *this;
     }
 
-    bool operator!=(const ListerIterator &other) const {
+    bool operator!=(const Iterator &other) const {
       return current_entry_ != std::nullopt ||
              other.current_entry_ != std::nullopt;
     }
 
    protected:
     // Only used for end iterator
-    ListerIterator(Lister &lister, bool /*end*/) : lister_(lister) {}
+    Iterator(Lister &lister, bool /*end*/) : lister_(lister) {}
 
    private:
     Lister &lister_;
@@ -312,10 +321,11 @@ class Lister {
    */
   std::optional<Entry> next();
 
-  ListerIterator begin() { return ListerIterator(*this); }
-  ListerIterator end() { return ListerIterator(*this, true); }
+  Iterator begin() { return Iterator(*this); }
+  Iterator end() { return Iterator(*this, true); }
 
  private:
-  rust::Box<opendal::ffi::Lister> raw_lister_;
+  std::unique_ptr<details::Lister> raw_lister_;
 };
+
 }  // namespace opendal
diff --git a/bindings/cpp/src/details/common.hpp b/bindings/cpp/src/details/common.hpp
new file mode 100644
index 000000000..bc0e44923
--- /dev/null
+++ b/bindings/cpp/src/details/common.hpp
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include "rust/cxx.h"
+
+namespace opendal::details {
+
+template <typename T>
+auto rust_str(T &&s) -> decltype(s.data(), s.size(), rust::Str()) {
+  return rust::Str(s.data(), s.size());
+}
+
+template <typename T>
+auto rust_string(T &&s) -> decltype(s.data(), s.size(), rust::String()) {
+  return rust::String(s.data(), s.size());
+}
+
+template <typename T, typename Container>
+auto rust_slice(Container &&s)
+    -> decltype(s.data(), s.size(), rust::Slice<T>()) {
+  using Elem = std::remove_pointer_t<decltype(s.data())>;
+  static_assert(std::is_convertible_v<Elem, T>,
+                "Container element type must be convertible to T");
+
+  return rust::Slice<T>(reinterpret_cast<T *>(s.data()), s.size());
+}
+
+}  // namespace opendal::details
diff --git a/bindings/cpp/src/details/lister.hpp b/bindings/cpp/src/details/lister.hpp
new file mode 100644
index 000000000..f135a3e26
--- /dev/null
+++ b/bindings/cpp/src/details/lister.hpp
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include "lib.rs.h"
+
+/**
+ * @class Lister
+ * @brief Lister is designed to list the entries of a directory.
+ * @details It provides next operation to get the next entry. You can also use
+ * it like an iterator.
+ * @code{.cpp}
+ * auto lister = operator.lister("dir/");
+ * for (const auto &entry : lister) {
+ *   // Do something with entry
+ * }
+ * @endcode
+ */
+namespace opendal::details {
+
+class Lister {
+ public:
+  Lister(rust::Box<opendal::ffi::Lister> &&lister)
+      : raw_lister_{std::move(lister)} {}
+
+  Lister(Lister &&) = default;
+
+  ~Lister() = default;
+
+  /**
+   * @brief Get the next entry of the lister
+   *
+   * @return The next entry of the lister
+   */
+  ffi::OptionalEntry next() { return raw_lister_->next(); }
+
+ private:
+  rust::Box<opendal::ffi::Lister> raw_lister_;
+};
+
+}  // namespace opendal::details
diff --git a/bindings/cpp/src/details/operator.cpp b/bindings/cpp/src/details/operator.cpp
new file mode 100644
index 000000000..3596c91ec
--- /dev/null
+++ b/bindings/cpp/src/details/operator.cpp
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "details/operator.hpp"
+
+#include "details/common.hpp"
+#include "lib.rs.h"
+
+namespace opendal::details {
+
+rust::Box<opendal::ffi::Operator> Operator::create_by_config(
+    std::string_view scheme,
+    const std::unordered_map<std::string, std::string> &config) {
+  auto rust_map = rust::Vec<ffi::HashMapValue>();
+  rust_map.reserve(config.size());
+  for (auto &[k, v] : config) {
+    rust_map.push_back({rust_string(k), rust_string(v)});
+  }
+
+  return opendal::ffi::new_operator(rust_str(scheme), rust_map);
+}
+
+std::string Operator::read(std::string_view path) {
+  auto rust_vec = operator_->read(rust_str(path));
+  return {rust_vec.data(), rust_vec.data() + rust_vec.size()};
+}
+
+void Operator::write(std::string_view path, std::string_view data) {
+  operator_->write(rust_str(path), rust_slice<const uint8_t>(data));
+}
+
+rust::Box<ffi::Reader> Operator::reader(std::string_view path) {
+  return operator_->reader(rust_str(path));
+}
+
+bool Operator::exists(std::string_view path) {
+  return operator_->exists(rust_str(path));
+}
+
+void Operator::create_dir(std::string_view path) {
+  operator_->create_dir(rust_str(path));
+}
+
+void Operator::copy(std::string_view src, std::string_view dst) {
+  operator_->copy(rust_str(src), rust_str(dst));
+}
+
+void Operator::rename(std::string_view src, std::string_view dst) {
+  operator_->rename(rust_str(src), rust_str(dst));
+}
+
+void Operator::remove(std::string_view path) {
+  operator_->remove(rust_str(path));
+}
+
+ffi::Metadata Operator::stat(std::string_view path) {
+  return operator_->stat(rust_str(path));
+}
+
+rust::Vec<ffi::Entry> Operator::list(std::string_view path) {
+  return operator_->list(rust_str(path));
+}
+
+rust::Box<ffi::Lister> Operator::lister(std::string_view path) {
+  return operator_->lister(rust_str(path));
+}
+
+}  // namespace opendal::details
diff --git a/bindings/cpp/src/details/operator.hpp b/bindings/cpp/src/details/operator.hpp
new file mode 100644
index 000000000..41ab9977b
--- /dev/null
+++ b/bindings/cpp/src/details/operator.hpp
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+#include "lib.rs.h"
+
+namespace opendal::details {
+
+class Operator {
+ public:
+  Operator(std::string_view scheme,
+           const std::unordered_map<std::string, std::string> &config = {})
+      : operator_(create_by_config(scheme, config)) {}
+
+  Operator(const Operator &) = delete;
+  Operator &operator=(const Operator &) = delete;
+
+  Operator(Operator &&) = default;
+  Operator &operator=(Operator &&) = default;
+  ~Operator() = default;
+
+  std::string read(std::string_view path);
+
+  void write(std::string_view path, std::string_view data);
+
+  rust::Box<ffi::Reader> reader(std::string_view path);
+
+  bool exists(std::string_view path);
+
+  void create_dir(std::string_view path);
+
+  void copy(std::string_view src, std::string_view dst);
+
+  void rename(std::string_view src, std::string_view dst);
+
+  void remove(std::string_view path);
+
+  ffi::Metadata stat(std::string_view path);
+
+  rust::Vec<ffi::Entry> list(std::string_view path);
+
+  rust::Box<ffi::Lister> lister(std::string_view path);
+
+ private:
+  using OperatorBox = rust::Box<opendal::ffi::Operator>;
+  using ConfigMap = std::unordered_map<std::string, std::string>;
+
+  OperatorBox create_by_config(std::string_view scheme,
+                               const ConfigMap &config);
+
+  OperatorBox operator_;
+};
+
+}  // namespace opendal::details
diff --git a/bindings/cpp/src/details/reader.cpp b/bindings/cpp/src/details/reader.cpp
new file mode 100644
index 000000000..8814a419b
--- /dev/null
+++ b/bindings/cpp/src/details/reader.cpp
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "details/reader.hpp"
+
+namespace opendal::details {
+
+ffi::SeekDir rust_seek_dir(std::ios_base::seekdir dir) {
+  switch (dir) {
+    case std::ios_base::beg:
+      return ffi::SeekDir::Start;
+
+    case std::ios_base::cur:
+      return ffi::SeekDir::Current;
+
+    case std::ios_base::end:
+      return ffi::SeekDir::End;
+
+    default:
+      throw std::runtime_error("invalid seekdir");
+  }
+}
+
+std::size_t Reader::read(void *s, std::size_t n) {
+  return reader_->read(rust::Slice<uint8_t>(static_cast<uint8_t *>(s), n));
+}
+
+std::uint64_t Reader::seek(std::uint64_t off, std::ios_base::seekdir dir) {
+  return reader_->seek(off, rust_seek_dir(dir));
+}
+
+}  // namespace opendal::details
diff --git a/bindings/cpp/src/details/reader.hpp b/bindings/cpp/src/details/reader.hpp
new file mode 100644
index 000000000..7516e3281
--- /dev/null
+++ b/bindings/cpp/src/details/reader.hpp
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#pragma once
+
+#include "lib.rs.h"
+
+namespace opendal::details {
+/**
+ * @class Reader
+ * @brief Reader is designed to read data from the operator.
+ * @details It provides basic read and seek operations. If you want to use it
+ * like a stream, you can use `ReaderStream` instead.
+ * @code{.cpp}
+ * opendal::ReaderStream stream(operator.reader("path"));
+ * @endcode
+ */
+class Reader {
+ public:
+  Reader(rust::Box<opendal::ffi::Reader> &&reader)
+      : reader_(std::move(reader)) {}
+
+  Reader(Reader &&) = default;
+
+  ~Reader() = default;
+
+  std::size_t read(void *s, std::size_t n);
+
+  std::uint64_t seek(std::uint64_t off, std::ios_base::seekdir dir);
+
+ private:
+  rust::Box<opendal::ffi::Reader> reader_;
+};
+
+}  // namespace opendal::details
diff --git a/bindings/cpp/src/opendal.cpp b/bindings/cpp/src/opendal.cpp
index c886647cb..3b079cd68 100644
--- a/bindings/cpp/src/opendal.cpp
+++ b/bindings/cpp/src/opendal.cpp
@@ -19,155 +19,146 @@
 
 #include "opendal.hpp"
 
-using namespace opendal;
+#include <memory>
 
-#define RUST_STR(s) rust::Str(s.data(), s.size())
-#define RUST_STRING(s) rust::String(s.data(), s.size())
+#include "boost/date_time/posix_time/time_parsers.hpp"
+#include "details/lister.hpp"
+#include "details/operator.hpp"
+#include "details/reader.hpp"
 
-// Operator
+namespace opendal {
 
-Operator::Operator(std::string_view scheme,
-                   const std::unordered_map<std::string, std::string> &config) {
-  auto rust_map = rust::Vec<ffi::HashMapValue>();
-  rust_map.reserve(config.size());
-  for (auto &[k, v] : config) {
-    rust_map.push_back({RUST_STRING(k), RUST_STRING(v)});
+std::optional<std::string> parse_optional_string(ffi::OptionalString &&s) {
+  if (s.has_value) {
+    return std::string(std::move(s.value));
+  } else {
+    return std::nullopt;
   }
+}
 
-  operator_ = opendal::ffi::new_operator(RUST_STR(scheme), rust_map);
+Entry parse_entry(ffi::Entry &&other) {
+  return Entry{
+      .path = std::string(std::move(other.path)),
+  };
 }
 
-bool Operator::available() const { return operator_.has_value(); }
+Metadata parse_meta_data(ffi::Metadata &&meta) {
+  Metadata metadata{
+      .type = static_cast<EntryMode>(meta.mode),
+      .content_length = meta.content_length,
+      .cache_control = parse_optional_string(std::move(meta.cache_control)),
+      .content_disposition =
+          parse_optional_string(std::move(meta.content_disposition)),
+      .content_md5 = parse_optional_string(std::move(meta.content_md5)),
+      .content_type = parse_optional_string(std::move(meta.content_type)),
+      .etag = parse_optional_string(std::move(meta.etag)),
+  };
 
-// We can't avoid copy, because std::vector hides the internal structure.
-// std::vector doesn't support init from a pointer without copy.
-std::vector<uint8_t> Operator::read(std::string_view path) {
-  auto rust_vec = operator_.value()->read(RUST_STR(path));
+  auto last_modified_str = parse_optional_string(std::move(meta.last_modified));
+  if (last_modified_str.has_value()) {
+    metadata.last_modified =
+        boost::posix_time::from_iso_string(last_modified_str.value());
+  }
 
-  return {rust_vec.data(), rust_vec.data() + rust_vec.size()};
+  return metadata;
 }
 
-void Operator::write(std::string_view path, const std::vector<uint8_t> &data) {
-  operator_.value()->write(
-      RUST_STR(path), rust::Slice<const uint8_t>(data.data(), data.size()));
+Operator::Operator() = default;
+
+Operator::Operator(std::string_view scheme,
+                   const std::unordered_map<std::string, std::string> &config) {
+  operator_ = std::make_unique<details::Operator>(scheme, config);
 }
 
-bool Operator::exists(std::string_view path) {
-  return operator_.value()->exists(RUST_STR(path));
+Operator::~Operator() = default;
+
+Operator::Operator(Operator &&) = default;
+Operator &Operator::operator=(Operator &&) = default;
+
+bool Operator::available() const { return operator_ != nullptr; }
+
+// We can't avoid copy, because std::vector hides the internal structure.
+// std::vector doesn't support init from a pointer without copy.
+std::string Operator::read(std::string_view path) {
+  return operator_->read(path);
 }
 
-bool Operator::is_exist(std::string_view path) {
-  return exists(path);
+void Operator::write(std::string_view path, std::string_view data) {
+  operator_->write(path, data);
 }
 
+bool Operator::exists(std::string_view path) { return operator_->exists(path); }
+
+bool Operator::is_exist(std::string_view path) { return exists(path); }
+
 void Operator::create_dir(std::string_view path) {
-  operator_.value()->create_dir(RUST_STR(path));
+  operator_->create_dir(path);
 }
 
 void Operator::copy(std::string_view src, std::string_view dst) {
-  operator_.value()->copy(RUST_STR(src), RUST_STR(dst));
+  operator_->copy(src, dst);
 }
 
 void Operator::rename(std::string_view src, std::string_view dst) {
-  operator_.value()->rename(RUST_STR(src), RUST_STR(dst));
+  operator_->rename(src, dst);
 }
 
-void Operator::remove(std::string_view path) {
-  operator_.value()->remove(RUST_STR(path));
-}
+void Operator::remove(std::string_view path) { operator_->remove(path); }
 
 Metadata Operator::stat(std::string_view path) {
-  return {operator_.value()->stat(RUST_STR(path))};
+  return parse_meta_data(operator_->stat(path));
 }
 
 std::vector<Entry> Operator::list(std::string_view path) {
-  auto rust_vec = operator_.value()->list(RUST_STR(path));
+  auto rust_vec = operator_->list(path);
 
   std::vector<Entry> entries;
   entries.reserve(rust_vec.size());
   for (auto &&entry : rust_vec) {
-    entries.push_back(std::move(entry));
+    entries.emplace_back(parse_entry(std::move(entry)));
   }
+
   return entries;
 }
 
 Lister Operator::lister(std::string_view path) {
-  return operator_.value()->lister(RUST_STR(path));
+  return std::make_unique<details::Lister>(operator_->lister(path));
 }
 
 Reader Operator::reader(std::string_view path) {
-  return operator_.value()->reader(RUST_STR(path));
+  return std::make_unique<details::Reader>(operator_->reader(path));
 }
 
-// Reader
+Lister::Lister(std::unique_ptr<details::Lister> lister)
+    : raw_lister_{std::move(lister)} {};
 
-std::streamsize Reader::read(void *s, std::streamsize n) {
-  auto rust_slice = rust::Slice<uint8_t>(reinterpret_cast<uint8_t *>(s), n);
-  auto read_size = raw_reader_->read(rust_slice);
-  return read_size;
-}
+Lister::Lister(Lister &&) = default;
 
-ffi::SeekDir to_rust_seek_dir(std::ios_base::seekdir dir);
-
-std::streampos Reader::seek(std::streamoff off, std::ios_base::seekdir dir) {
-  return raw_reader_->seek(off, to_rust_seek_dir(dir));
-}
-
-// Lister
+Lister::~Lister() = default;
 
 std::optional<Entry> Lister::next() {
   auto entry = raw_lister_->next();
-  if (entry.has_value) {
-    return std::move(entry.value);
-  } else {
+
+  if (!entry.has_value) {
     return std::nullopt;
   }
-}
-
-// Metadata
 
-std::optional<std::string> parse_optional_string(ffi::OptionalString &&s);
-
-Metadata::Metadata(ffi::Metadata &&other) {
-  type = static_cast<EntryMode>(other.mode);
-  content_length = other.content_length;
-  cache_control = parse_optional_string(std::move(other.cache_control));
-  content_disposition =
-      parse_optional_string(std::move(other.content_disposition));
-  content_type = parse_optional_string(std::move(other.content_type));
-  content_md5 = parse_optional_string(std::move(other.content_md5));
-  etag = parse_optional_string(std::move(other.etag));
-  auto last_modified_str =
-      parse_optional_string(std::move(other.last_modified));
-  if (last_modified_str.has_value()) {
-    last_modified =
-        boost::posix_time::from_iso_string(last_modified_str.value());
-  }
+  return parse_entry(std::move(entry.value));
 }
 
-// Entry
+Reader::Reader(std::unique_ptr<details::Reader> &&reader)
+    : raw_reader_{std::move(reader)} {}
 
-Entry::Entry(ffi::Entry &&other) : path(std::move(other.path)) {}
+Reader::Reader(Reader &&) = default;
 
-// helper functions
+Reader::~Reader() = default;
 
-std::optional<std::string> parse_optional_string(ffi::OptionalString &&s) {
-  if (s.has_value) {
-    return std::string(std::move(s.value));
-  } else {
-    return std::nullopt;
-  }
+std::streamsize Reader::read(void *s, std::streamsize n) {
+  return raw_reader_->read(s, n);
 }
 
-ffi::SeekDir to_rust_seek_dir(std::ios_base::seekdir dir) {
-  switch (dir) {
-    case std::ios_base::beg:
-      return ffi::SeekDir::Start;
-    case std::ios_base::cur:
-      return ffi::SeekDir::Current;
-    case std::ios_base::end:
-      return ffi::SeekDir::End;
-    default:
-      throw std::runtime_error("invalid seekdir");
-  }
-}
\ No newline at end of file
+std::streampos Reader::seek(std::streamoff off, std::ios_base::seekdir dir) {
+  return raw_reader_->seek(off, dir);
+}
+
+}  // namespace opendal
diff --git a/bindings/cpp/tests/basic_test.cpp b/bindings/cpp/tests/basic_test.cpp
index 9ffd03df3..c67ceaae1 100644
--- a/bindings/cpp/tests/basic_test.cpp
+++ b/bindings/cpp/tests/basic_test.cpp
@@ -18,7 +18,6 @@
  */
 
 #include <ctime>
-#include <optional>
 #include <random>
 #include <string>
 #include <unordered_map>
@@ -52,7 +51,7 @@ TEST_F(OpendalTest, BasicTest) {
   std::string file_path_copied = "test_copied";
   std::string file_path_renamed = "test_renamed";
   std::string dir_path = "test_dir/";
-  std::vector<uint8_t> data = {1, 2, 3, 4, 5};
+  std::string_view data = "abc";
 
   // write
   op.write(file_path, data);
@@ -94,7 +93,7 @@ TEST_F(OpendalTest, BasicTest) {
 TEST_F(OpendalTest, ReaderTest) {
   std::string file_path = "test";
   constexpr int size = 2000;
-  std::vector<uint8_t> data(size);
+  std::string data(size, 0);
 
   for (auto &d : data) {
     d = rng() % 256;
@@ -105,9 +104,10 @@ TEST_F(OpendalTest, ReaderTest) {
 
   // reader
   auto reader = op.reader(file_path);
-  uint8_t part_data[100];
+  // uint8_t part_data[100];
+  std::string part_data(100, 0);
   reader.seek(200, std::ios::cur);
-  reader.read(part_data, 100);
+  reader.read(part_data.data(), 100);
   EXPECT_EQ(reader.seek(0, std::ios::cur), 300);
   for (int i = 0; i < 100; ++i) {
     EXPECT_EQ(part_data[i], data[200 + i]);
@@ -115,7 +115,7 @@ TEST_F(OpendalTest, ReaderTest) {
   reader.seek(0, std::ios::beg);
 
   // stream
-  opendal::ReaderStream stream(std::move(reader));
+  opendal::ReaderStream stream(op.reader(file_path));
 
   auto read_fn = [&](std::size_t to_read, std::streampos expected_tellg) {
     std::vector<char> v(to_read);
@@ -133,7 +133,7 @@ TEST_F(OpendalTest, ReaderTest) {
   read_fn(1000, 1041);
 
   stream.seekg(0, std::ios::beg);
-  std::vector<uint8_t> reader_data(std::istreambuf_iterator<char>{stream}, {});
+  std::string reader_data(std::istreambuf_iterator<char>{stream}, {});
   EXPECT_EQ(reader_data, data);
 }
 
@@ -141,9 +141,9 @@ TEST_F(OpendalTest, ListerTest) {
   std::string dir_path = "test_dir/";
   op.create_dir(dir_path);
   auto test1_path = dir_path + "test1";
-  op.write(test1_path, {1, 2, 3});
+  op.write(test1_path, "123");
   auto test2_path = dir_path + "test2";
-  op.write(test2_path, {4, 5, 6});
+  op.write(test2_path, "456");
 
   auto lister = op.lister("test_dir/");
 
diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt
index a7fcaec03..4c06bdba0 100644
--- a/examples/cpp/CMakeLists.txt
+++ b/examples/cpp/CMakeLists.txt
@@ -3,14 +3,8 @@ project(opendal-cpp-examples)
 
 set(CMAKE_CXX_STANDARD 17)
 
-include(FetchContent)
-FetchContent_Declare(
-  opendal-cpp
-  GIT_REPOSITORY https://github.com/apache/opendal.git
-  GIT_TAG        v0.52.0
-  SOURCE_SUBDIR  bindings/cpp
-)
-FetchContent_MakeAvailable(opendal-cpp)
+# use local bindings/cpp directory
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../bindings/cpp opendal_cpp_build)
 
 add_executable(basic-example basic.cpp)
 target_link_libraries(basic-example opendal_cpp)
diff --git a/examples/cpp/basic.cpp b/examples/cpp/basic.cpp
index 8fc52f824..2a87aeccf 100644
--- a/examples/cpp/basic.cpp
+++ b/examples/cpp/basic.cpp
@@ -1,11 +1,10 @@
 #include "opendal.hpp"
 
 #include <string>
-#include <vector>
 #include <iostream>
 
 int main() {
-  std::vector<uint8_t> data = {'a', 'b', 'c'};
+  std::string_view data = "abc";
 
   // Init operator
   opendal::Operator op = opendal::Operator("memory");
@@ -15,11 +14,17 @@ int main() {
 
   // Read data from operator
   auto res = op.read("test"); // res == data
+  std::cout << res << std::endl;
 
   // Using reader
   auto reader = op.reader("test");
-  opendal::ReaderStream stream(std::move(reader));
-  std::string res2;
-  stream >> res2; // res2 == "abc"
+  std::string res2(3, 0);
+  reader.read(res2.data(), data.size()); // res2 == "abc"
   std::cout << res2 << std::endl;
+
+  // Using reader stream
+  opendal::ReaderStream stream(op.reader("test"));
+  std::string res3;
+  stream >> res3; // res3 == "abc"
+  std::cout << res3 << std::endl;
 }