Skip to content

Expose a way to bypass is_trivially_destructible/_move_constructible check on extern types passed by value #359

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
dtolnay opened this issue Oct 12, 2020 · 3 comments · Fixed by #406

Comments

@dtolnay
Copy link
Owner

dtolnay commented Oct 12, 2020

As discussed in #339.

@dtolnay
Copy link
Owner Author

dtolnay commented Oct 12, 2020

Proof of concept:

#include <iostream>
#include <string>
#include <type_traits>

namespace rust {

namespace detail {
template <typename... Ts> struct make_void { using type = void; };
template <typename... Ts> using void_t = typename make_void<Ts...>::type;

template <typename Void, template <typename...> class, typename...>
struct detect : std::false_type {};
template <template <typename...> class T, typename... A>
struct detect<void_t<T<A...>>, T, A...> : std::true_type {};

template <template <typename...> class T, typename... A>
using is_detected = detect<void, T, A...>;

template <typename T> using detect_IsRelocatable = typename T::IsRelocatable;
template <typename T>
struct get_IsRelocatable
    : std::is_same<typename T::IsRelocatable, std::true_type> {};
} // namespace detail

template <typename T>
struct IsRelocatable
    : std::conditional<
          detail::is_detected<detail::detect_IsRelocatable, T>::value,
          detail::get_IsRelocatable<T>,
          std::integral_constant<
              bool, std::is_trivially_move_constructible<T>::value &&
                        std::is_trivially_destructible<T>::value>>::type {};

} // namespace rust

struct O { int32_t x; };
struct P { std::string s; };
struct Q; template <> struct rust::IsRelocatable<Q> : std::true_type {};
struct R { ~R(); };
struct S { ~S(); using IsRelocatable = std::true_type; };

int main() {
  std::cout << rust::IsRelocatable<O>::value << std::endl;
  std::cout << rust::IsRelocatable<P>::value << std::endl;
  std::cout << rust::IsRelocatable<Q>::value << std::endl;
  std::cout << rust::IsRelocatable<R>::value << std::endl;
  std::cout << rust::IsRelocatable<S>::value << std::endl;
}

@scott-wilson
Copy link

I tested this code, and this could be an issue for this use case:

struct ImplCopy {
    int32_t x;
    ImplCopy(const ImplCopy &obj): x(obj.x) {}
};

std::cout << rust::IsRelocatable<ImplCopy>::value << std::endl;  // returns 0

As far as I can tell, this should still be a trivial move in this specific case, but isn't because of the copy constructor.

@dtolnay
Copy link
Owner Author

dtolnay commented Oct 19, 2020

That is the intended behavior!

ImplCopy is extra wacky as far as C++ type go because it can only be copy constructed -- the user-defined copy constructor implicitly deletes the default constructor and disables aggregate initialization. A hypothetical use for such a type would for library-level capabilities: code having possession of an ImplCopy has "permission" to call certain APIs, can share that same permission to other code by passing a copy of its ImplCopy, and library code can require that its callers prove they've received "permission" by taking an ImplCopy argument back in. Where it gets great is the ImplCopy library can use the copy constructor to track the entire chain of who gave permission to whom, all the way from when the permission was originally given out by the library to when it was proven later by a caller.

Letting Rust move one of them around with no constructor call would defeat this.

class CapabilityLog {
  friend struct ImplCopy;
  static std::vector<std::tuple<const ImplCopy*, const ImplCopy*>> log_;
};

struct ImplCopy {
  int32_t x;
  ImplCopy(const ImplCopy &obj) {
    CapabilityLog::log_.emplace_back(&obj, this);
  }
};

The opt out required in the proof of concept (#359 (comment)) is:

  // if you own the type
  struct ImplCopy {
    int32_t x;
    ImplCopy(const ImplCopy &obj);
+   using IsRelocatable = std::true_type;
  };
  // otherwise
+ template <> struct rust::IsRelocatable<ImplCopy> : std::true_type {};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants