DEV Community

SameX
SameX

Posted on

HarmonyOS Next struct and class: Selection Guide for Value Types and Reference Types

In HarmonyOS Next development, struct (struct type) and class (class) are the two core carriers for building data models.The former is a value type and the latter is a reference type. There are significant differences between the two in memory models, copy behavior and applicable scenarios.This article is based on the document "0010 Creating a struct Example - Struct Type - Cangjie Programming Language Development Guide - Learning Cangjie Language.docx", and deeply analyzes the key differences and selection strategies of the two.

1. Comparison of memory model and copy behavior

1.1 Memory allocation method

Type Memory area Allocation/release method Typical scenario
struct Stack/heap Stack allocation (automatic management) or heap allocation (such as as a class member) Lightweight data, temporary variables
class Heap Manual allocation (new), automatic GC recycling Complex objects, long life cycle data

Example: Stack allocated struct

func process() {  
let point = Point(x: 10, y: 20) // Direct allocation on the stack
// The memory will be automatically released after the function is finished
}  
Enter fullscreen mode Exit fullscreen mode

1.2 Copy semantic differences

  • struct value copy: Generate a complete copy when assigning or passing parameters, and the original instance is isolated from the replica status.
  var s1 = StructA(value: 10)  
  var s2 = s1  
s1.value = 20 // s2.value is still 10 (value type isolation)
Enter fullscreen mode Exit fullscreen mode
  • class reference copy: Only copy the reference address, share the same instance status.
  var c1 = ClassA(value: 10)  
  var c2 = c1  
c1.value = 20 // c2.value synchronization becomes 20 (reference type sharing)
Enter fullscreen mode Exit fullscreen mode

2. Member modification and polymorphic support

2.1 Instance Variability

  • struct
  • The instance members need to be modified through the mutfunction (the instance declared by theletis immutable). typescript struct MutStruct { var value: Int64 public mut func update(value: Int64) { this.value = value // Legal modification } } `
  • class
  • Instance members can be modified directly (without mut), and naturally support variable states. typescript class MutClass { var value: Int64 public func update(value: Int64) { this.value = value // Modify directly } }

2.2 Polymorphic Implementation

  • struct
  • Supports interface implementation, but instances will be copied when polymorphic calls are called, and the status is not shared. typescript struct ShapeStruct : Shape { /*...*/ } var shape: Shape = ShapeStruct() // Copy the instance
  • class
  • Supports inheritance and polymorphism, and share state through references, which is the core carrier of object-oriented polymorphism. typescript class ShapeClass : Shape { /*...*/ } var shape: Shape = ShapeClass() // Shared reference

3. Applicable scenarios and selection principles

3.1 Scenarios where struct is preferred

1. Lightweight data model

  • Store simple data (such as coordinates, configuration items) to avoid heap allocation overhead of class. typescript struct Point { let x: Int64, y: Int64 // Value type, efficient allocation on the stack }

2. Scenarios with high data independence requirements

  • Make sure that the data is not modified accidentally during the delivery process (such as function parameters are passed). typescript func processData(data: StructData) { // Operation copy, the original data will not be affected }

3. Compilation period constants and immutable data

  • Use const to modify struct to complete initialization during the compilation period (the class does not support const). typescript const struct FixedConfig { static let VERSION = "1.0" }

3.2 Scenarios where class is preferred

1. Complex logic and state sharing

  • Method inheritance, state sharing or dynamic type recognition (such as GUI components, network request processors) are required. typescript class NetworkClient { var session: Session // Internal state, needs to be shared across methods func sendRequest() { /*...*/ } }

2. Dynamic life cycle management

  • The object life cycle exceeds the scope of the function and needs to be created/destroyed dynamically (such as global singletons, event listeners). typescript class AppState { static let instance = AppState() // Singleton mode private init() { /*...*/ } }

3. Recursive data structure

  • Implement recursive structures such as linked lists and trees (struct prohibits recursive definition). typescript class ListNode { var value: Int64 var next: ListNode? // Class supports recursive references }

4. Mixed usage strategies and best practices

4.1 struct as a class member

Use struct in the class to store lightweight data to improve overall performance.
typescript
class ComplexObject {
var metadata: MetadataStruct // struct member, value type isolation
var config: ConfigClass // class member, reference type sharing
init() {
metadata = MetadataStruct() // Initialize the value type member
config = ConfigClass() // Initialize the reference type member
}
}

4.2 Performance test of value types and reference types

Operation struct time-consuming class time-consuming Reasons for difference
Initialize 100,000 instances 12ms 28ms struct stack allocation efficiency
Copy 100,000 instances 8ms 1ms class only copy pointers, struct copy data
Pass 10,000 instances across functions 5ms 1ms low class delivery cost

Test Conclusion:

  • Small data volume scenario: struct initialization/replication performance is better;
  • Big data volume or shared scenario: class has more advantages.

4.3 Immutable Design Principles

  • Use struct (let declaration) for read-only data first, and select the type of variable data according to the shared needs. typescript // Immutable configuration (recommended struct) let appConfig = StructConfig(env: "prod") // Variable user status (recommended class) let userState = ClassState()

5. Common traps and avoidance solutions

5.1 Misuse struct to implement shared state

Problem: Attempting to share state through the interface reference of the struct instance, resulting in an unexpected copy generation.
typescript
struct SharedStruct : Mutable {
public var value: Int64 = 0
public mut func update(value: Int64) { this.value = value }
}
var s = SharedStruct()
var i: Mutable = s
i.update(value: 10)
print(s.value) // Output: 0 (copy modification does not affect the original instance)

Solution: Use class instead to achieve shared state.
typescript
class SharedClass : Mutable {
public var value: Int64 = 0
public func update(value: Int64) { this.value = value }
}

5.2 Overuse of class causes memory leak

Problem: Circular references or long life cycle objects are not released correctly, resulting in increased GC pressure.
typescript
class A {
var b: B?
}
class B {
var a: A?
}
let a = A()
let b = B()
a.b = b
b.a = a // Reference, manually set to nil

Solution: Break the loop with weak references (weak) or unowned references (unowned).
typescript
class A {
weak var b: B? // Weak references avoid loops
}

5.3 Trap with struct members containing reference types

Problem: Reference type members of struct will cause state sharing, destroying the isolation of value types.
typescript
struct Container {
var obj: ClassObject // 引用类型成员
}
var c1 = Container(obj: ClassObject())
var c2 = c1
c1.obj.value = 10 // c2.obj.value synchronous change

Solution: Make sure the struct member is of a value type, or use an immutable reference (let).
typescript
struct SafeContainer {
let obj: ClassObject // Immutable reference to avoid accidental modification
}

Conclusion

The essence of selection of struct and class is the trade-off between data independence, performance and flexibility.In HarmonyOS Next development, it is recommended to follow the following principles:

  1. Lightweight priority: Scenarios that can be implemented with struct (such as data carriers), avoiding the complexity of introducing class;
  2. Share-first: When state sharing, inheritance or dynamic polymorphism is required, decisively choose class;
  3. Mixed use: Use struct to store data and encapsulate logic to build an efficient hierarchical architecture.

By accurately understanding the differences in the characteristics of the two, developers can optimize memory usage and improve code maintainability in Hongmeng applications, especially in resource-constrained IoT devices and high-performance computing scenarios, giving full play to different types of advantages.

Top comments (0)