mirror of
				https://github.com/bspeice/speice.io
				synced 2025-11-03 10:00:37 -05:00 
			
		
		
		
	Continuing to flesh this out
This commit is contained in:
		@ -6,6 +6,9 @@ category:
 | 
				
			|||||||
tags: [python]
 | 
					tags: [python]
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Other languages have done similar things (interfaces in Java), but think the Rust comparison is
 | 
				
			||||||
 | 
					useful because both languages are "system."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Simple Example
 | 
					# Simple Example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Accept parameter types, return known type.
 | 
					Accept parameter types, return known type.
 | 
				
			||||||
@ -20,6 +23,8 @@ Same parameter signature, but return different types - `AsRef`
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Arbitrary `self`
 | 
					# Arbitrary `self`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Forms the basis for Rust's async system, but used very rarely aside from that.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[`std::enable_shared_from_this`](https://en.cppreference.com/w/cpp/memory/enable_shared_from_this)
 | 
					[`std::enable_shared_from_this`](https://en.cppreference.com/w/cpp/memory/enable_shared_from_this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
`enable_unique_from_this` doesn't make a whole lot of sense, but Rust can do it:
 | 
					`enable_unique_from_this` doesn't make a whole lot of sense, but Rust can do it:
 | 
				
			||||||
@ -102,7 +107,8 @@ impl MyTrait for Box<MyStruct> {
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Just have to make sure that `MyTrait` is in scope all the time, and that's not fun. Ultimately, Rust
 | 
					Just have to make sure that `MyTrait` is in scope all the time, and that's not fun. Ultimately, Rust
 | 
				
			||||||
kinda already has UFCS.
 | 
					kinda already has UFCS. It's only "kinda" because you have to bring it in scope, and it's
 | 
				
			||||||
 | 
					potentially unclear when it's being used (extension traits), but it does get the basic job done.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Default implementation
 | 
					# Default implementation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -154,7 +160,7 @@ Shouldn't be too hard - `T::some_method()` should be compilable.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
`noexcept`, etc.
 | 
					`noexcept`, etc.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Local trait implementation of remote types?
 | 
					# Local trait implementation of remote data type?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AKA "extension methods". UFCS can accomplish this, and could use free functions to handle instead,
 | 
					AKA "extension methods". UFCS can accomplish this, and could use free functions to handle instead,
 | 
				
			||||||
but having the IDE auto-complete `.<the next thing>` is exceedingly useful, as opposed to memorizing
 | 
					but having the IDE auto-complete `.<the next thing>` is exceedingly useful, as opposed to memorizing
 | 
				
			||||||
@ -162,7 +168,60 @@ what functions are necessary for conversion. We're not changing what's possible,
 | 
				
			|||||||
easier for humans.
 | 
					easier for humans.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Likely requires sub-classing the remote class. Implicit conversions don't _really_ work because they
 | 
					Likely requires sub-classing the remote class. Implicit conversions don't _really_ work because they
 | 
				
			||||||
must be defined on the remote type.
 | 
					must be defined on the remote type (not true: `operator Local` must be defined on remote, but
 | 
				
			||||||
 | 
					`Local` could have a `Local(const Remote&)` implicit constructor). Could maybe use wrapper classes
 | 
				
			||||||
 | 
					that have single-arg (implicit) constructors, and get away with it as long as the wrapper knows it's
 | 
				
			||||||
 | 
					not safe to modify the internals. That said, wrapper can only use the public interface unless
 | 
				
			||||||
 | 
					declared friend (which is no different to Rust).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```c++
 | 
				
			||||||
 | 
					#include <concepts>
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SomeRemoteClass {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template<typename T>
 | 
				
			||||||
 | 
					concept MyConcept = requires (T a) {
 | 
				
			||||||
 | 
					    { a.do_something() } -> std::same_as<std::uint64_t>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Note: It's unsafe to move `SomeRemoteClass`, so we accept by reference
 | 
				
			||||||
 | 
					// Requiring SomeRemoteClass be copy-constructible would also be OK.
 | 
				
			||||||
 | 
					class LocalImpl {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    LocalImpl(const SomeRemoteClass &remote): remote_{remote} {};
 | 
				
			||||||
 | 
					    std::uint64_t do_something() {
 | 
				
			||||||
 | 
					        return 42;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    const SomeRemoteClass &remote_;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto auto_func(MyConcept auto value) {
 | 
				
			||||||
 | 
					    auto x = value.do_something();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void regular_func(LocalImpl value) {
 | 
				
			||||||
 | 
					    auto x = value.do_something();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int main() {
 | 
				
			||||||
 | 
					    SomeRemoteClass x {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // This isn't OK because `auto` doesn't automatically convert to `LocalImpl`
 | 
				
			||||||
 | 
					    //auto_func(x);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // This _is_ OK because we explicitly declare the class we want (`LocalImpl`) and `SomeRemoteClass`
 | 
				
			||||||
 | 
					    // is implicitly converted. Just so happens that `LocalImpl` implements `MyConcept`.
 | 
				
			||||||
 | 
					    regular_func(x);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We could extend the conversion pattern using specializations of `LocalImpl`, or maybe use
 | 
				
			||||||
 | 
					    // `std::variant` to hold different internal types, but there's still a disconnect between
 | 
				
			||||||
 | 
					    // what we actually want to fulfill (`MyConcept`) and how that's implemented for remote types
 | 
				
			||||||
 | 
					    // (using the `LocalImpl` wrapper and implicit conversions).
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Rust makes this weird because you have to `use ClientExt` to bring the methods in scope, but the
 | 
					Rust makes this weird because you have to `use ClientExt` to bring the methods in scope, but the
 | 
				
			||||||
trait name might not show up because `impl ClientExt for RemoteStruct` is defined elsewhere.
 | 
					trait name might not show up because `impl ClientExt for RemoteStruct` is defined elsewhere.
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user