Skip to content
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

Efficient code reuse #349

Open
nrc opened this issue Oct 3, 2014 · 64 comments
Open

Efficient code reuse #349

nrc opened this issue Oct 3, 2014 · 64 comments
Labels
A-data-types RFCs about data-types A-traits Trait system related proposals & ideas A-typesystem Type system related proposals & ideas postponed RFCs that have been postponed and may be revisited at a later time. T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@nrc
Copy link
Member

nrc commented Oct 3, 2014

Motivation

Data structures which closely fit a single inheritance model can be very efficiently implemented in C++. Where high performance (both space and time) is crucial there is distinct disadvantage in using Rust for programs which widely use such data structures. A pressing example is the DOM in Servo. For a small example in C++, see https://gist.github.com/jdm/9900569. We require some solution which satisfies the following requirements:

  • cheap field access from internal methods;
  • cheap dynamic dispatch of methods;
  • cheap downcasting;
  • thin pointers;
  • sharing of fields and methods between definitions;
  • safe, i.e., doesn't require a bunch of transmutes or other unsafe code to be usable;
  • syntactically lightweight or implicit upcasting;
  • calling functions through smartpointers, e.g. fn foo(JSRef<T>, ...);
  • static dispatch of methods.

Status

There has been discussion of potential solutions on discuss (http://discuss.rust-lang.org/t/summary-of-efficient-inheritance-rfcs/494) and in several meetings (minutes and minutes).

We clarified the requirements listed above (see the minutes for details) and established that an ergonomic solution is required. That is, we explicitly don't want to discourage programmers from using this feature by having an unfriendly syntax. We also summarised and evaluated the various proposals (again, see the minutes for details). We feel that no proposal 'as is' is totally satisfactory and that there is a bunch of work to do to get a good solution. We established a timeline (see below) for design and implementation. We would like to reserve a few keywords to reduce the backwards compatibility hazard (#342).

Plan

In December the Rust and Servo teams will all be in one place and we intend to make decisions on how to provide an efficient code reuse solution and plan the implementation in detail. We'll take into account the discussions on the various RFC and discuss comment threads and of course all the community members who attend the Rust weekly meetings will be invited. We will take and publish minutes. This will lead to a new RFC. We expect implementation work to start post-1.0. If we identify backwards compatibility hazards, then we'll aim to address these before the 1.0 RC.

RFC PRs

There have been numerous RFC PRs for different solutions to this problem. All of these have had useful and interesting parts and earlier RFCs have been heavily cannibalised by later ones. We believe that RFC PRs #245 and #250 are the most relevant and the eventual solution will come from these PRs and/or any ideas that emerge in the future. For a summary of some of the proposals and some discussion, see this discuss thread.

@nrc nrc added the postponed RFCs that have been postponed and may be revisited at a later time. label Oct 3, 2014
@brendanzab
Copy link
Member

Comment moved to discuss.

@why-jay
Copy link

why-jay commented Dec 23, 2014

In December the Rust and Servo teams will all be in one place and we intend to make decisions on how to provide an efficient code reuse solution and plan the implementation in detail.

What ended up happening? :)

@huonw
Copy link
Member

huonw commented Dec 24, 2014

That paragraph is unfortunately outdated: we'd actually already postponed all discussion to after 1.0 since we believe any changes/features we will add are backwards compatible and there's a lot of more urgent (I.e. backwards incompatible) work that took priority.

@why-jay
Copy link

why-jay commented Dec 24, 2014

I understand. I'm excited to see what ends up being implemented.

@breckinloggins
Copy link

One thing I love about rust is that its non-free abstractions are usually explicit. I love knowing that a struct is "just a struct". It would be nice to say that features requiring runtime and fancy "under the hood" data representation support (for some value of fancy) are built from pluggable, orthogonal components.

So how about something like "anonymous composition is inheritance"? The idea would be that

struct Node {
    // stuff
}

struct FooNode {
   Node;
   // more stuff
}

implies a specific struct layout with the requisite syntactic sugar. For simplicity let's suppose that we only allow one anonymous component per struct.

EDIT: It was pointed out to me by @steveklabnik and others that rust has had trait objects since forever. I'll keep the following bit in place and just say that std::raw::TraitObject is the kind of thing I would love to be not only "pluggable" but "leave-out-able", in the sense that if I'm writing an OS and I can't implement that yet, I can tell the compiler that it can't make me any TraitObjects and that it wouldn't be ok for me to use them right now.

For dispatch, it would be nice if we could plug-in and compose the dispatch resolution mechanism. I don't want the "doesn't play well with others" feel of C++ vtables. What if I want to back this part of the language with the Objective-C runtime? Or Glib? The current "personality" of rust feels like this should be possible, in the same way that "give me your allocator function and then I'll let you use boxed things" works.

I guess my main point is that rust is the first language in a long time where I really feel like the modern features of the language don't come with being chained to a runtime layout and functionality set that's given from up on high by the Gods of Rust.

I would love it if the rust team could implement functionality like this while still retaining that ethos.

@pcwalton
Copy link
Contributor

Virtual dispatch will of course be explicit under this proposal or any other.

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label May 15, 2015
@steveklabnik
Copy link
Member

@sighoya
Copy link

sighoya commented Mar 11, 2018

What's the state in 2018?

Couldn't we agree to any "inheritance" proposal?

@blueridanus
Copy link

blueridanus commented Apr 12, 2018

What happened to @nikomatsakis's proposal?

I'm trying to create a general UI framework for Rust, basically based on push-pull FRP. There's no way to do it, however, because there's no way to model a type hierarchy: enums don't actually "add" types together, and the nodes of enums aren't really types, only tags.

@Centril Centril added A-data-types RFCs about data-types A-traits Trait system related proposals & ideas A-typesystem Type system related proposals & ideas labels Nov 27, 2018
@LifeIsStrange
Copy link

Now that rust 2018 edition happened, shouldn't the priority be bumped up?

This is my main limiting factor and probably the last big remaining reason for outsiders to not learn rust...

@moonheart08
Copy link

It's been five years. Isn't it about time this is un-postponed?

@aldanor
Copy link

aldanor commented Nov 24, 2020

@ibraheemdev

But why is there not an easy way to compose structs like this?

Well, if you insist on composition...

struct Animal {
    pub name: String,
}

impl Animal {
    pub fn print_name(&self) {
        println!("{}", self.name);
    }
}

struct Dog {
    owner: String,
    animal: Animal,
}

impl std::ops::Deref for Dog {
    type Target = Animal;
    
    fn deref(&self) -> &Self::Target { &self.animal }
}

fn main() {
    let dog = Dog { 
        owner: "John".into(), 
        animal: Animal { name: "Ripley".into() },
    };
    dog.print_name();
    println!("{}, {}", dog.name, dog.owner);
}

@ibraheemdev
Copy link
Member

@aldanor Using Deref is quite limited and is widely regarded as a bad practice. This is not a solution to composition. My point is that because Rust prefers composition over inheritance it should have first class language constructs to aid in composition and efficient code reuse.

@ssokolow
Copy link

ssokolow commented Nov 24, 2020

From what I remember, last it was discussed at length, the result was "Desirable, but not as easy as it sounds and we have things that are needed more urgently, like async/await, to get implemented first".

(i.e. There's no single objectively superior way to implement a code reuse mechanism, so more discussion is needed to decide which trade-offs are best to enshrine in the core language as the blessed primary way to do it.)

@burdges
Copy link

burdges commented Nov 24, 2020

There was a consensus that delegation should be explored outside rustc first, ala delegate. We've enough plausible delegation patterns that practical exploration helps. I suppose doing delegation via proc macros could expose more proc macro shortcomings too, which then improves proc macros and better supports whatever complex delegation scenarios rustc excludes.

I frequently flip the type structure wherever I'd otherwise be tempted by simple local inheritance:

struct MuSigInner { .. }
pub struct MuSigCommitStage { inner: MuSigInner, .. }
pub struct MuSigRevealStage { inner: MuSigInner, .. }
pub struct MuSigSignStage { inner: MuSigInner, .. }

vs

pub struct MuSig<Stage> { .. }
pub struct CommitStage { .. }
pub struct RevealStage { .. }
pub struct SignStage { .. }

Any methods you want inherited from MuSigInner in the first simply become generic methods in impl<Stage> MuSig<Stage> in the second. In this way, all your methods become more explicit about their provenance and role, which avoids inheritance bugs and confusion.

@dbsxdbsx
Copy link

@ibraheemdev

But why is there not an easy way to compose structs like this?

Well, if you insist on composition...

struct Animal {
    pub name: String,
}

impl Animal {
    pub fn print_name(&self) {
        println!("{}", self.name);
    }
}

struct Dog {
    owner: String,
    animal: Animal,
}

impl std::ops::Deref for Dog {
    type Target = Animal;
    
    fn deref(&self) -> &Self::Target { &self.animal }
}

fn main() {
    let dog = Dog { 
        owner: "John".into(), 
        animal: Animal { name: "Ripley".into() },
    };
    dog.print_name();
    println!("{}, {}", dog.name, dog.owner);
}

I think this way is the best workaround. But still, that working out this issue by adding field in trait would be more consistent with the design of rust.

@Serentty
Copy link

I wonder if properties (which would be allowed in traits) would be a good option. I'm quite fond of them, but I've heard opposition to them for being too magical. I disagree, but oh well.

@DanielJoyce
Copy link

What if Animal is defined in another crate, and Dog is defined in your crate. And some point down the line, Animal adds a field Dog already has. I think this gets into the same kind of thinking around the current orphan rules and coherence.

@Iron-E
Copy link

Iron-E commented Jun 13, 2022

@dbsxdbsx @ibraheemdev I personally would implement that like this:

struct Animal {
    pub name: String,
}

trait AnimalExt {
  fn get_animal(&self) -> &Animal;
  fn get_name(&self) -> &str {
    &self.get_animal().name
  }
}

impl AnimalExt for Animal {
  fn get_animal(&self) -> &Animal {
    self
  }
}

struct Dog {
    owner: String,
    animal: Animal,
}

impl AnimalExt for Dog {
  fn get_animal(&self) -> &Animal {
    &self.animal
  }
}

fn main() {
  let dog = Dog { 
    owner: "John".into(), 
    animal: Animal { name: "Ripley".into() }
  };
  println!("{}", dog.get_name());
  println!("{}, {}", dog.get_name(), dog.owner);
}

I think it is more scalable for the number of methods and does not abuse Deref— you can make a method that accepts impl AnimalExt as a parameter and it will be valid for both Animal and Dog.

@SOF3
Copy link

SOF3 commented Jun 14, 2022

While many people suggest abusing Deref for code reuse, it is actually both ergonomic and idiomatic to use AsRef for a similar purpose:

fn feed(mut animal: impl AsMut<Animal>) {
    animal.as_mut().food += 1;
}

struct Dog {
    animal: Animal,
    other_fields: Xxx,
}

impl AsMut<Animal> for Dog {
    fn as_mut(&mut self) -> &mut Animal { &mut self.animal }
}

let dog: Dog = unimplemented!();

feed(&mut dog);

This problem is already well-solved with std::convert traits. All it takes is a slight change in the method parameter signature. Why need so much complexity?

@mrahhal
Copy link

mrahhal commented Jun 14, 2022

@SOF3 This is a nice none-hacky approach but how do you solve wanting to match on a base type and do different things depending on the sub type?

let base = get_some_subtype();

match base {
  SubType1 => ...
  SubType2 => ...
}

This is why I want code reuse in the first place. I want to be able to fully access info about both the base and sub types, especially to match on base types and do branching logic (no matter whether that is implemented through inheritance or composition).

@SOF3
Copy link

SOF3 commented Jun 14, 2022

@SOF3 This is a nice none-hacky approach but how do you solve wanting to match on a base type and do different things depending on the sub type?

let base = get_some_subtype();

match base {
  SubType1 => ...
  SubType2 => ...
}

This is why I want code reuse in the first place. I want to be able to fully access info about both the base and sub types, especially to match on base types and do branching logic (no matter whether that is implemented through inheritance or composition).

What is the type of base? You can't own a dynamically sized type. You need an enum for that, which we already can match.

@mrahhal
Copy link

mrahhal commented Jun 14, 2022

By matching on a ref (by having a discriminant of some kind similar to enums) in some way maybe. The snippet above isn't rust sanitized and haven't thought it through much. What I mean is I've been wanting a way to achieve that as a result (not through enums, but through inherited/composited types), disregarding the method/syntax, and we don't have that yet.

@SOF3
Copy link

SOF3 commented Jun 14, 2022

By matching on a ref (by having a discriminant of some kind similar to enums) in some way maybe. The snippet above isn't rust sanitized and haven't thought it through much. What I mean is I've been wanting a way to achieve that as a result (not through enums, but through inherited/composited types), disregarding the method/syntax, and we don't have that yet.

that is specialization, which is an antipattern. why not use enums for that? alternatively, make it a method in Animal/AminalExt?

although unidiomatic, this can also be achieved through Any::downcast_mut() in an if-let-else-if-let chain.

@mrahhal
Copy link

mrahhal commented Jun 14, 2022

Enums don't really solve all cases relating to this. Just look at how rustc tries really hard to emulate inheritance by using enums for the different nodes in the tree (and one of the members talked exactly about this case when they were discussing specialization in the past). Enums just are not always the answer. Whether it's called specialization or something else, rust is in serious need for a better way to compose shared code, is what I'm saying. Some workarounds work for the simpler cases (enums, deref, AsRef), but they're not nearly enough.

@SOF3
Copy link

SOF3 commented Jun 14, 2022

I am not sure what the problem encountered in rustc is, but last time i had to work on a flow analysis in Java, I really wished they were enums.

@Iron-E
Copy link

Iron-E commented Jun 14, 2022

This is a nice none-hacky approach but how do you solve wanting to match on a base type and do different things depending on the sub type?

let base = get_some_subtype();

match base {
  SubType1 => ...
  SubType2 => ...
}

@mrahhal I made a comment on Reddit about this issue not too long ago. In short, it's not related to struct inheritance, but the fact you can't downcast any-old trait object (e.g. &dyn Foo) in Rust— only &dyn Any and &dyn Error (afaik). Hopefully that changes in the future!

@mrahhal
Copy link

mrahhal commented Jun 14, 2022

@Iron-E Thanks, I didn't know about this. But it's not related to the code reuse issue. Traits unify signatures and are not the answer for code reuse. I'm not saying it should be related to inheritance either, the solution could very well be through composition. In summary, need a code reuse solution (won't repeat what this issue lists as requirements) that also provides the ability to pattern match on type kinds (a type kind's meaning is an impl detail). (But thanks for the downcast pointer, might be useful in certain cases)

It's unfortunate that this has been in stagnation since 2014. Rust is great, but this is one of the biggest blockers if you're modeling certain complex logic that wants some kind of code reuse, without having to resort to an amalgamation mess of unreadable macros.

@SOF3
Copy link

SOF3 commented Jun 16, 2022

provides the ability to pattern match on type kinds

I still don't see what's missing with enums. Code reuse through enum delegation could be convenient given the appropriate codegen mechanisms (such as a decl macro derived from a proc macro).

@mrahhal
Copy link

mrahhal commented Jun 16, 2022

One example is when you want to restrict types. Imagine building a tree model for a compiler and having specific nodes (IfStatementNode, UnaryExpressionNode, etc) representing the structure, you want a few base node types that reuse some logic, and then you want to store specific node types in other nodes (or simply accept certain types in functions). You can't do any of this in rust today. (yes, I know I can nest structs inside of each other, but people reaching out to Deref hacks is proof this isn't a valid solution for many cases)

The way rustc and servo work around this is by having multiple enums (Statement, Expression, etc) and compromising by restricting to the common denominator enum (Expression for example), but this results in a lot of annoyances throughout the code (it's not strong typed). The problem is not specific to wanting to model intrinsically hierarchical models, but it is a pain there in particular.

And as I just noted above, I don't want macros to solve common reusability needs.

@fogti
Copy link

fogti commented Jun 16, 2022

@mrahhal a partial solution for this would be combining enum-derive (or something like that) to expose traits defined for contained types, and introducing a way to flatten enums (so, like kind of inheritance for enums, the enums which "get" (?) inherited from must be exhaustive; similar to the "mixin" concept in other languages), like:

enum UnaryExpr {
    A, B,
}
enum BinaryExpr {
    X, Y,
}
#[inherit(UnaryExpr, BinaryExpr)]
enum Expr {}
// `Expr` contains variants `UnaryExpr`, `BinaryExpr`

@mrahhal
Copy link

mrahhal commented Jun 16, 2022

@zseri I've explored many solutions including this. It's still far from what I want. Yes you can easily create a visitor that exposes a "flattened" match on the type, but that alone doesn't solve it. Even if Expr contains a UnaryExpr variant, UnaryExpr itself is not an Expr. This means I can't share common functionality (again, without resorting to messy macros and a lot of boilerplate). I also wouldn't say this is equivalent to mixins. You're just nesting types inside other types here, whereas mixins are pretty much composition of logic inside the same type.

Right now I implement something similar to what rustc does, multiple enums and a few macros to handle some of the logic reuse (no tool can semantically parse macros so somehow we're back to using eval("{code as a string}") everywhere). I tried every kind of workaround you can think of, none can replace a proper code reuse feature.

@wandercn
Copy link

wandercn commented Jun 19, 2022

我认为写一个完整一点的例子比较起来比较直观, @ibraheemdev @Iron-E @mrahhal @zseri

以下是golang代码通过结构体内嵌实现的属性和方法复用代码方式,这是golang面向对象的处理方式,确实很方便。怎么转换到 rust的 trait方式复用代码呢?
rust中实现的时候把golang中的baseRequest结构体的方法,抽象到trait BaseRequestExt中,trait BaseRequestExt中的 fn base(&self) 和 fn base_as_mut(&mut self) 方法,需要在每次复用的时候重写。其他的代码直接复用trait BaseRequestExt中的方法,我觉得还是非常高效的。

impl BaseRequestExt for SendSmsRequest {
    fn base(&self) -> &BaseRequest {
        self.rpcRequest.base()
    }

    fn base_as_mut(&mut self) -> &mut BaseRequest {
        self.rpcRequest.base_as_mut()
    }
}

详细的对照代码如下:

  1. go实现面向对象复用代码的例子
// base class
type baseRequest struct {
	Scheme         string
	Method         string
	Domain         string
	Port           string
	RegionId       string
	ReadTimeout    time.Duration
	ConnectTimeout time.Duration
	isInsecure     *bool

	userAgent map[string]string
	product   string
	version   string

	actionName string

	AcceptFormat string

	QueryParams map[string]string
	Headers     map[string]string
	FormParams  map[string]string
	Content     []byte

	locationServiceCode  string
	locationEndpointType string

	queries string

	stringToSign string
}

func (request *baseRequest) GetQueryParams() map[string]string {
	return request.QueryParams
}

func (request *baseRequest) GetFormParams() map[string]string {
	return request.FormParams
}

func (request *baseRequest) GetReadTimeout() time.Duration {
	return request.ReadTimeout
}

func (request *baseRequest) GetConnectTimeout() time.Duration {
	return request.ConnectTimeout
}

func (request *baseRequest) SetReadTimeout(readTimeout time.Duration) {
	request.ReadTimeout = readTimeout
}

func (request *baseRequest) SetConnectTimeout(connectTimeout time.Duration) {
	request.ConnectTimeout = connectTimeout
}

func (request *baseRequest) GetHTTPSInsecure() *bool {
	return request.isInsecure
}

func (request *baseRequest) SetHTTPSInsecure(isInsecure bool) {
	request.isInsecure = &isInsecure
}

func (request *baseRequest) GetContent() []byte {
	return request.Content
}

func (request *baseRequest) SetVersion(version string) {
	request.version = version
}

func (request *baseRequest) GetVersion() string {
	return request.version
}

func (request *baseRequest) GetActionName() string {
	return request.actionName
}

func (request *baseRequest) SetContent(content []byte) {
	request.Content = content
}

func (request *baseRequest) GetUserAgent() map[string]string {
	return request.userAgent
}

func (request *baseRequest) AppendUserAgent(key, value string) {
	newkey := true
	if request.userAgent == nil {
		request.userAgent = make(map[string]string)
	}
	if strings.ToLower(key) != "core" && strings.ToLower(key) != "go" {
		for tag, _ := range request.userAgent {
			if tag == key {
				request.userAgent[tag] = value
				newkey = false
			}
		}
		if newkey {
			request.userAgent[key] = value
		}
	}
}

func (request *baseRequest) addHeaderParam(key, value string) {
	request.Headers[key] = value
}

func (request *baseRequest) addQueryParam(key, value string) {
	request.QueryParams[key] = value
}

func (request *baseRequest) addFormParam(key, value string) {
	request.FormParams[key] = value
}

func (request *baseRequest) GetAcceptFormat() string {
	return request.AcceptFormat
}

func (request *baseRequest) GetLocationServiceCode() string {
	return request.locationServiceCode
}

func (request *baseRequest) GetLocationEndpointType() string {
	return request.locationEndpointType
}

func (request *baseRequest) GetProduct() string {
	return request.product
}

func (request *baseRequest) GetScheme() string {
	return request.Scheme
}

func (request *baseRequest) SetScheme(scheme string) {
	request.Scheme = scheme
}

func (request *baseRequest) GetMethod() string {
	return request.Method
}

func (request *baseRequest) GetDomain() string {
	return request.Domain
}

func (request *baseRequest) SetDomain(host string) {
	request.Domain = host
}

func (request *baseRequest) GetPort() string {
	return request.Port
}

func (request *baseRequest) GetRegionId() string {
	return request.RegionId
}

func (request *baseRequest) GetHeaders() map[string]string {
	return request.Headers
}

func (request *baseRequest) SetContentType(contentType string) {
	request.addHeaderParam("Content-Type", contentType)
}

func (request *baseRequest) GetContentType() (contentType string, contains bool) {
	contentType, contains = request.Headers["Content-Type"]
	return
}

func (request *baseRequest) SetStringToSign(stringToSign string) {
	request.stringToSign = stringToSign
}

func (request *baseRequest) GetStringToSign() string {
	return request.stringToSign
}

type RpcRequest struct {
	*baseRequest
}

type CommonRequest struct {
	*baseRequest

	Version      string
	ApiName      string
	Product      string
	ServiceCode  string
	EndpointType string

	// roa params
	PathPattern string
	PathParams  map[string]string
}


// SendSmsRequest is the request struct for api SendSms
type SendSmsRequest struct {
	*requests.RpcRequest
	ResourceOwnerId      requests.Integer `position:"Query" name:"ResourceOwnerId"`
	SmsUpExtendCode      string           `position:"Query" name:"SmsUpExtendCode"`
	SignName             string           `position:"Query" name:"SignName"`
	ResourceOwnerAccount string           `position:"Query" name:"ResourceOwnerAccount"`
	PhoneNumbers         string           `position:"Query" name:"PhoneNumbers"`
	OwnerId              requests.Integer `position:"Query" name:"OwnerId"`
	OutId                string           `position:"Query" name:"OutId"`
	TemplateCode         string           `position:"Query" name:"TemplateCode"`
	TemplateParam        string           `position:"Query" name:"TemplateParam"`
}
  1. 对应rust中通过trait 实现面向对象复用代码的对照代码例子。
#[derive(Default, Debug)]
pub struct BaseRequest {
    pub Scheme: String,
    pub Method: String,
    pub Domain: String,
    pub Port: String,
    pub RegionId: String,
    pub isInsecure: bool,

    pub userAgent: HashMap<String, String>,
    pub product: String,
    pub version: String,

    pub actionName: String,

    pub AcceptFormat: String,

    pub QueryParams: HashMap<String, String>,
    pub Headers: HashMap<String, String>,
    pub FormParams: HashMap<String, String>,
    pub Content: Vec<u8>,

    pub locationServiceCode: String,
    pub locationEndpointType: String,

    pub queries: String,

    pub stringToSign: String,
}

pub trait BaseRequestExt {
    fn base(&self) -> &BaseRequest;

    fn base_as_mut(&mut self) -> &mut BaseRequest;

    fn GetQueryParams(&self) -> &HashMap<String, String> {
        self.base().QueryParams.borrow()
    }

    fn GetFormParams(&self) -> &HashMap<String, String> {
        self.base().FormParams.borrow()
    }

    fn GetHTTPSInsecure(&self) -> bool {
        self.base().isInsecure
    }

    fn SetHTTPSInsecure(&mut self, isInsecure: bool) {
        self.base_as_mut().isInsecure = isInsecure
    }

    fn GetContent(&self) -> &[u8] {
        self.base().Content.borrow()
    }

    fn SetContent(&mut self, content: &[u8]) {
        self.base_as_mut().Content = content.to_owned()
    }

    fn SetVersion(&mut self, version: &str) {
        self.base_as_mut().version = version.to_string();
    }

    fn GetVersion(&self) -> &str {
        self.base().version.borrow()
    }

    fn GetActionName(&self) -> &str {
        self.base().actionName.borrow()
    }

    fn GetUserAgent(&self) -> &HashMap<String, String> {
        self.base().userAgent.borrow()
    }

    fn AppendUserAgent(&mut self, key: &str, value: &str) {
        let mut newKey = true;
        if self.base_as_mut().userAgent.is_empty() {
            self.base_as_mut().userAgent = HashMap::new();
        }
        if strings::ToLower(key).as_str() != "core" && strings::ToLower(key) != "rust" {
            for (tag, mut v) in self.base_as_mut().userAgent.iter_mut() {
                if tag == key {
                    *v = value.to_string();
                    newKey = false;
                }
            }
            if newKey {
                self.base_as_mut()
                    .userAgent
                    .insert(key.to_string(), value.to_string());
            }
        }
    }

    fn addHeaderParam(&mut self, key: &str, value: &str) {
        self.base_as_mut()
            .Headers
            .insert(key.to_string(), value.to_string());
    }

    fn addQueryParam(&mut self, key: &str, value: &str) {
        self.base_as_mut()
            .QueryParams
            .insert(key.to_string(), value.to_string());
    }

    fn addFormParam(&mut self, key: &str, value: &str) {
        self.base_as_mut()
            .FormParams
            .insert(key.to_string(), value.to_string());
    }

    fn GetAcceptFormat(&self) -> &str {
        self.base().AcceptFormat.borrow()
    }

    fn GetLocationServiceCode(&self) -> &str {
        self.base().locationServiceCode.borrow()
    }

    fn GetLocationEndpointType(&self) -> &str {
        self.base().locationEndpointType.borrow()
    }

    fn GetProduct(&self) -> &str {
        self.base().product.borrow()
    }

    fn SetProduct(&mut self, product: &str) {
        self.base_as_mut().product = product.to_string();
    }

    fn GetScheme(&self) -> &str {
        self.base().Scheme.borrow()
    }

    fn SetScheme(&mut self, scheme: &str) {
        self.base_as_mut().Scheme = scheme.to_string()
    }

    fn GetMethod(&self) -> &str {
        self.base().Method.borrow()
    }

    fn GetDomain(&self) -> &str {
        self.base().Domain.borrow()
    }

    fn SetDomain(&mut self, host: &str) {
        self.base_as_mut().Domain = host.to_string()
    }

    fn GetPort(&self) -> &str {
        self.base().Port.borrow()
    }

    fn GetRegionId(&self) -> &str {
        self.base().RegionId.borrow()
    }

    fn GetHeaders(&self) -> &HashMap<String, String> {
        self.base().Headers.borrow()
    }

    fn SetContentType(&mut self, contentType: &str) {
        self.addHeaderParam("Content-Type", contentType)
    }

    fn GetContentType(&self) -> Option<&str> {
        self.base().Headers.get("Content-Type").map(|s| s.as_str())
    }

    fn SetStringToSign(&mut self, stringToSign: &str) {
        self.base_as_mut().stringToSign = stringToSign.to_string()
    }

    fn GetStringToSign(&self) -> &str {
        self.base().stringToSign.borrow()
    }
}

#[derive(Default, Debug)]
pub struct RpcRequest {
    base: BaseRequest,
}

impl BaseRequestExt for RpcRequest {
    fn base(&self) -> &BaseRequest {
        self.base.borrow()
    }

    fn base_as_mut(&mut self) -> &mut BaseRequest {
        self.base.borrow_mut()
    }
}

pub struct CommonRequest {
    base: BaseRequest,
    pub Version: String,
    pub ApiName: String,
    pub Product: String,
    pub ServiceCode: String,
    pub EndpointType: String,

    // roa params
    pub PathPattern: String,
    pub PathParams: HashMap<String, String>,

    pub Ontology: AcsRequest,
}

impl BaseRequestExt for CommonRequest {
    fn base(&self) -> &BaseRequest {
        self.base.borrow()
    }

    fn base_as_mut(&mut self) -> &mut BaseRequest {
        self.base.borrow_mut()
    }
}

#[derive(Default, Debug)]
pub struct SendSmsRequest {
    pub rpcRequest: requests::RpcRequest,
    pub ResourceOwnerId: requests::Integer, //`position:"Query" name:"ResourceOwnerId"`
    pub SmsUpExtendCode: String,            //`position:"Query" name:"SmsUpExtendCode"`
    pub SignName: String,                   //`position:"Query" name:"SignName"`
    pub ResourceOwnerAccount: String,       //`position:"Query" name:"ResourceOwnerAccount"`
    pub PhoneNumbers: String,               //`position:"Query" name:"PhoneNumbers"`
    pub OwnerId: requests::Integer,         //`position:"Query" name:"OwnerId"`
    pub OutId: String,                      //`position:"Query" name:"OutId"`
    pub TemplateCode: String,               //`position:"Query" name:"TemplateCode"`
    pub TemplateParam: String,              //`position:"Query" name:"TemplateParam"`
}

impl BaseRequestExt for SendSmsRequest {
    fn base(&self) -> &BaseRequest {
        self.rpcRequest.base()
    }

    fn base_as_mut(&mut self) -> &mut BaseRequest {
        self.rpcRequest.base_as_mut()
    }
}
impl SendSmsRequest {
    pub fn BuildQueryParams(&mut self) {
        self.addQueryParam("SignName", &self.SignName.to_owned());
        self.addQueryParam("PhoneNumbers", &self.PhoneNumbers.to_owned());
        self.addQueryParam("TemplateCode", &self.TemplateCode.to_owned());
        self.addQueryParam("ResourceOwnerId", &self.ResourceOwnerId.to_owned());
        self.addQueryParam("SmsUpExtendCode", &self.SmsUpExtendCode.to_owned());
        self.addQueryParam(
            "ResourceOwnerAccount",
            &self.ResourceOwnerAccount.to_owned(),
        );
        self.addQueryParam("OwnerId", &self.OwnerId.to_owned());
        self.addQueryParam("OutId", &self.OutId.to_owned());
        self.addQueryParam("TemplateParam", &self.TemplateParam.to_owned());
    }
}

@fogti
Copy link

fogti commented Jun 19, 2022

@Iron-E regarding

you can't downcast any-old trait object (e.g. &dyn Foo) in Rust— only &dyn Any and &dyn Error (afaik). Hopefully that changes in the future!

although this isn't solved automatically, the downcast-rs, as-any (the second crate, written by myself, is a bit simpler, and less powerful) crates partially solve this.

@SOF3
Copy link

SOF3 commented Jun 21, 2022

@wandercn The question is, why do we need to use SendSmsRequest itself as a BaseRequest? Why can't users just call request.base() directly?

@wandercn
Copy link

wandercn commented Jun 21, 2022

@SOF3
就是为了实现跟golang一样的内嵌结构体,直接组合复用代码,组合看成一个整体。

pub struct SendSmsRequest {
         pub base BaseRequest
         pub comm CommRequest
         pub other OtherRequest
}

let request =SendSmsRequest::default();
调用组合的方法可以类似如下这样,把组合进来的方法都看成是SendSmsRequest的方法,使用的人不需要知道方法内部具体来源直接看成一个整体。:
request.baseFunc() ;
request.commFunc();
request.otherFunc();

避免下面的方式:
request.base().Func() ;
request.comm().Func();
request.other().Func();
如果每个组合的base ,comm,other也有组合别的结构体。
调用内部方法可能变成:
request.base().base().a()...Func() ;
request.base().base().b()...Func() ;
request.comm().comm().c().....Func();
request.comm().comm().d().....Func();
request.other().other().e().......Func();
request.other().other().f().......Func();

@12101111
Copy link

@wandercn You can use this crate https://crates.io/crates/delegate

@SOF3
Copy link

SOF3 commented Jun 22, 2022

@wandercn That's what I'm saying, why do we want composite structures? How is request.base_func() any better than request.base.func()?

Even in OOP languages like Java, it is typically considered an antipattern to have deep hierarchies of inheritance. Composite structures do not save us from all the issues of inheritance. Having base nested deeply inside is probably not that sensible anyway.

Instead of

type Foo struct { Base, Other Fields }
type Bar struct { Foo, Other Fields }

Why not

type Foo struct { Other Fields }
type Bar struct { Base, Foo, Other Fields }

? At least in that case it is more explicit that Foo itself should be used as a component instead of a request itself, because a Bar request is indeed not a Foo request, and it would be confusing for a Bar request to contain a Foo request. I am not sure whether self-descriptive type system is a popular concept in the Go language, but for Rust, many APIs are already clear enough what they intend to do just by looking at the type signature, so if you have a function that accepts a HorseRequest, you probably expect that an actual, self-contained HorseRequest was sent, and if someone actually sent a WhiteHorseRequest that contains a HorseRequest, you would end up with a condition where 白馬非馬. One confusing example is like this:

struct Request {
    uid: u128,
}
struct NewHorseRequest {
    base: Request,
    name: String,
}
struct NewWhiteHorseRequest {
    base: HorseRequest,
    whiteness: f32,
}

fn handle_horse(horse: &HorseRequest) {
    colorizer.set_color(&horse.name, random_color());
}

handle_horse expects to assign a color to a horse request. Originally it couldn't go wrong. But since a NewWhiteHorseRequest also contains a NewHorseRequest, we end up allowing people to misuse the handle_horse API for white horse requests, which should originally be impossible.

To conclude, having a type contain a type that contains another type (i.e. more than one level of composition) while the outermost type has direct relationship to the innermost type is an antipattern, no matter we have code reuse (through inheritance or struct composition) or not. This antipattern just becomes typically less obvious in Java, Go, etc. because people are too used to the ambiguous information represented by type information, but becomes more apparent in Rust because people are writing code "correctly" (my subjective opinion) such that self-explanatory function signatures become possible.

@dbsxdbsx
Copy link

There is discussion of delegation in the language in #2393 and discussions of delgation proc macros like delegate linked from https://www.reddit.com/r/rust/comments/ccqucx/delegation_macro/etpfud5/?utm_source=reddit&utm_medium=web2x&context=3

Using this crate is still the best way I found to reuse code, at present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-data-types RFCs about data-types A-traits Trait system related proposals & ideas A-typesystem Type system related proposals & ideas postponed RFCs that have been postponed and may be revisited at a later time. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests