Cross-Platform
Cangjie provides cross-platform development capabilities that address code reuse issues in cross-end development scenarios. Users can differentiate between common code and platform-specific code to share code across different platforms, reducing the time spent on developing and maintaining identical code for different platforms.
Note:
The cross-platform development feature is experimental, and using it may involve risks.
Introduction to Cross-Platform Development Features
Common Code and Platform-Specific Code
The platform-agnostic part of a codebase is referred to as common code, which contains code that can run on all target platforms. This typically includes algorithms, business logic, or other modules that do not depend on specific platform functionalities. The platform-dependent part of a codebase is referred to as platform-specific code, which contains code that can only run on specific platforms. This usually involves calls to operating systems, hardware, or other platform-specific functionalities. Both common code and platform-specific code belong to the same package. Platform files can depend on common files, but common files cannot depend on platform files. Common code is used for sharing across different platforms and can be marked with the common modifier. Platform-specific code is used to distinguish implementations for different platforms and can be marked with the platform modifier. The following rules apply when using the common/specific modifiers:
- The
commonmodifier can only appear in common code, and thespecificmodifier can only appear in platform-specific code. - The
common/specificmodifiers conflict withprivate/const/foreignmodifiers and cannot be used simultaneously.
The following example defines common code and a global function foo:
package cmp
public common func foo(): Unit {
println("I am common")
}
The following example defines platform-specific code and a global function foo:
package cmp
public specific func foo(): Unit {
println("I am platform")
}
More details will be described in the cross-platform development chapter.
Types Supporting Cross-Platform Development Features
Below are detailed usage rules for types that support cross-platform development features.
Global Functions
Global functions support cross-platform features. Users can use the common and specific modifiers for global functions.
A common global function may or may not include an implementation.
common func foo(): Int64
common func goo(a: Int64): Int64 { 1 }
In the above example, two common global functions are defined. The function foo has no function body, while goo includes a function body. Both are valid definitions of common global functions.
common/specific global functions must adhere to the following restrictions:
- A
commonglobal function must specify its return type. - If a
commonglobal function has a complete implementation, aspecificglobal function is not required. If acommonglobal function lacks a complete implementation, aspecificglobal function must be defined. - The function signature of a
platformglobal function must match that of the correspondingcommonglobal function in the same package, meaning parameter types and return types must be consistent. Additionally, the following rules must be satisfied:- The
commonglobal function and the corresponding platform global function must use the same modifiers (e.g.,public,unsafe, etc.), except forcommon/specific. - If the
commonglobal function uses named parameters, the corresponding positions in thespecificglobal function must use parameters with the same names. - If the
commonglobal function includes default values, the corresponding positions in thespecificglobal function must use named parameters with the same names. Default values are not supported inspecificglobal functions. - Each
specificglobal function must match a uniquecommonglobal function. Multiple platform global functions cannot match the samecommonglobal function.
- The
Example:
In a common file, some common global functions can be defined:
// common file
pkg cjmp
common func foo1() // error: 'common' function return type must be specified
common func foo2(): Unit // ok
common func foo3(a!: Int64): Unit // ok
common func foo4(a!: Int64 = 1): Unit // ok
common func foo5(a: Int64): Unit { println("hello word") } // ok
In a platform file, specific global functions can be defined based on the common global functions:
// specific file
pkg cjmp
specific func foo2(a: Int64): Unit {} // error: different arguments
specific func foo2(): Int64 {} // error: different return type
public specific func foo2(): Int64 {} // error: different modifiers
specific func foo2(): Unit {} // ok
specific func foo3(a!: Int64): Unit { println("hello word") } // ok
specific func foo4(a!: Int64 = 1): Unit {} error: 'specific' function parameter can not have default value
specific func foo4(a!: Int64): Unit {} // ok
// common func foo5 has a complete implementation, so no platform definition is needed.
class
Cangjie classes support cross-platform features. Users can use the common and specific modifiers for classes and their members.
// common file
package cmp
common class A {
common var a: Int64 = 1
common init()
common func foo(): Unit
common prop p: Int64
}
// specific file
package cmp
specific class A {
specific var a: Int64 = 2
specific init() {}
specific func foo(): Unit {}
specific prop p: Int64 {
get() { a }
}
}
If a common class exists, there must be a matching specific class with the following requirements:
- The visibility of the
common classandspecific classmust be the same. - The interface implementation of the
common classandspecific classmust be the same. - The inheritance of the
common classandspecific classmust be the same. - A
common open classmatches aspecific open class. - A
common abstract classmatches aspecific abstract class. - A
common sealed abstract classmatches aspecific sealed abstract class.
Class Constructors
Constructors and primary constructors support cross-platform features. The following requirements must be met:
- A
common initcan have a concrete implementation or only a function signature, with the implementation provided byspecific init. - If a
common inithas a complete implementation, thespecific initcan be omitted. Otherwise, a matchingspecific initmust exist. - The visibility of
common initandspecific initmust be the same. - The
specific initimplementation overrides thecommon initimplementation. - The rules for primary constructors are the same as for constructors.
common/specificclasses support regular constructors, which can be defined in eithercommonorspecificclasses.- At least one explicitly defined constructor must exist in the
commonorspecificclass. - Static initializers cannot be modified with
common/specific.
// common file
package cmp
common class A {
common A()
common init(a: String) {}
init(a: Bool) {}
}
// specific file
package cmp
specific class A {
specific A() {}
specific init(a: String) {
println(a)
}
init(a: Int64) {}
}
Class Member Variables
common and specific class member variables must adhere to the following restrictions:
common/specificmember variables must specify their types.- The type, mutability, and visibility of
commonandspecificmember variables must be the same. - A
commonmember variable can be initialized directly or in a constructor, or it can only declare the type and be initialized in thespecificside. common/specificclasses support regular member variables, which can be defined in eithercommonorspecificclasses.- Static member variables of classes do not currently support cross-platform features but will be supported in future versions.
// common file
package cmp
common class A {
common let a: Int64 = 1
common var b: Int64
common var c: Int64
init() {
b = 1
c = 1
}
}
// specific file
package cmp
specific class A {
specific let a: Int64 = 2
specific let b: Int64 = 2
init(input: Int64) { c = input }
}
Class Member Functions
common and specific class member functions must adhere to the following restrictions:
- A
commonmember function can have a concrete implementation or only a function signature, with the implementation provided by aspecificmember function. - If a
commonmember function has a complete implementation, thespecificmember function can be omitted. Otherwise, a matchingspecificmember function must exist. - The parameters, return type, and modifiers (except
common/specific) ofcommonandspecificmember functions must be the same. common/specificclasses support regular member functions, which can be defined in eithercommonorspecificclasses.
// common file
package cmp
common class A {
common func foo1(a: Int64): Unit
common func foo2(): Unit {}
common func foo3(): Unit {}
func foo4() {}
}
// specific file
package cmp
specific class A {
specific func foo1(a: Int64): Unit { println(a) }
specific func foo3(): Unit { println("platform") }
func foo5(): Int64 { 1 }
init() {}
}
Class Properties
common and specific class properties must adhere to the following restrictions:
- A
commonproperty can have a concrete implementation or only a property signature, with the implementation provided by aspecificproperty. - If a
commonproperty has a complete implementation, thespecificproperty can be omitted. Otherwise, a matchingspecificproperty must exist. - The type, visibility, and assignability of
commonandspecificproperties must be the same. common/specificclasses support regular properties, which can be defined in eithercommonorspecificclasses.
// common file
package cmp
common class A {
common prop a: Int64
common prop b: Int64 {
get() { 1 }
}
common prop c: Int64 {
get() { 1 }
}
prop d: Int64 {
get() { 1 }
}
}
// specific file
package cmp
specific class A {
specific prop a: Int64 {
get() { 1 }
}
specific prop c: Int64 {
get() { 2 }
}
prop e: Int64 {
get() { 1 }
}
init() {}
}
Class Inheritance
Inheritance for common/specific classes does not currently support cross-platform features but will be supported in future versions.
struct
Cangjie structs support cross-platform features. Users can use the common and specific modifiers for structs and their members.
// common file
package cmp
common struct A {
common var a: Int64 = 1
common init()
common func foo(): Unit
common prop p: Int64
}
// specific file
package cmp
specific struct A {
specific var a: Int64 = 2
specific init() {}
specific func foo(): Unit {}
specific prop p: Int64 {
get() { a }
}
}
If a common struct exists, there must be a matching specific struct with the following requirements:
- The visibility of the
common structandspecific structmust be the same. - The interface implementation of the
common structandspecific structmust be the same. - The
common structandspecific structmust both be annotated with@Cor neither.
Struct Constructors
Constructors support cross-platform features. The following requirements must be met:
- A
common initcan have a concrete implementation or only a function signature, with the implementation provided byspecific init. - If a
common inithas a complete implementation, thespecific initcan be omitted. Otherwise, a matchingspecific initmust exist. - The visibility of
common initandspecific initmust be the same. - The
specific initimplementation overrides thecommon initimplementation. common/specificstructs support regular constructors, which can be defined in eithercommonorspecificstructs.- Static initializers cannot be modified with
common/specific.
// common file
package cmp
common struct A {
common init(a: String) {}
init(a: Bool) {}
}
// specific file
package cmp
specific struct A {
specific init(a: String) {
println(a)
}
init(a: Int64) {}
}
Struct Member Variables
common and specific struct member variables must adhere to the following restrictions:
common/specificmember variables must specify their types.- The type, mutability, and visibility of
commonandspecificmember variables must be the same. - A
commonmember variable can be initialized directly or in a constructor, or it can only declare the type and be initialized in thespecificside. common/specificstructs support regular member variables, which can be defined in eithercommonorspecificstructs.- Static member variables of structs do not currently support cross-platform features but will be supported in future versions.
// common file
package cmp
common struct A {
common let a: Int64 = 1
common var b: Int64
common var c: Int64
init() {
b = 1
c = 1
}
}
// specific file
package cmp
specific struct A {
specific let a: Int64 = 2
specific let b: Int64 = 2
init(input: Int64) { c = input }
}
Struct Member Functions
common and specific struct member functions must adhere to the following restrictions:
- A
commonmember function can have a concrete implementation or only a function signature, with the implementation provided by aspecificmember function. - If a
commonmember function has a complete implementation, thespecificmember function can be omitted. Otherwise, a matchingspecificmember function must exist. - The parameters, return type, and modifiers (except
common/specific) ofcommonandspecificmember functions must be the same. common/specificstructs support regular member functions, which can be defined in eithercommonorspecificstructs.
// common file
package cmp
common struct A {
common func foo1(a: Int64): Unit
common func foo2(): Unit {}
common func foo3(): Unit {}
func foo4() {}
}
// specific file
package cmp
specific struct A {
specific func foo1(a: Int64): Unit { println(a) }
specific func foo3(): Unit { println("platform") }
func foo5(): Int64 { 1 }
init() {}
}
Struct Properties
common and specific struct properties must adhere to the following restrictions:
- A
commonproperty can have a concrete implementation or only a property signature, with the implementation provided by aspecificproperty. - If a
commonproperty has a complete implementation, thespecificproperty can be omitted. Otherwise, a matchingspecificproperty must exist. - The type, visibility, and assignability of
commonandspecificproperties must be the same. common/specificstructs support regular properties, which can be defined in eithercommonorspecificstructs.
// common file
package cmp
common struct A {
common prop a: Int64
common prop b: Int64 {
get() { 1 }
}
common prop c: Int64 {
get() { 1 }
}
prop d: Int64 {
get() { 1 }
}
}
// specific file
package cmp
specific struct A {
specific prop a: Int64 {
get() { 1 }
}
specific prop c: Int64 {
get() { 2 }
}
prop e: Int64 {
get() { 1 }
}
init() {}
}
enum
Cangjie enums support cross-platform features. Users can use the common and specific modifiers for enums and their members.
// common file
package cmp
common enum A {
| ELEMENT
common func foo(): Unit
common prop p: Int64
}
// specific file
package cmp
specific enum A {
| ELEMENT
specific func foo(): Unit {}
specific prop p: Int64 {
get() { 1 }
}
}
If a common enum exists, there must be a matching specific enum with the following requirements:
- The visibility of the
common enumandspecific enummust be the same. - The interface implementation of the
common enumandspecific enummust be the same. - The corresponding constructors in the
common enumandspecific enummust be of the same type. - If the
common enumis an exhaustive enum, thespecific enummust also be exhaustive. If thecommon enumis non-exhaustive, thespecific enumcan be exhaustive.- For exhaustive enums, the
specific enummust include all constructors from thecommon enumand cannot add new constructors. - For non-exhaustive enums, the
specific enummust include all constructors from thecommon enumand can add new constructors.
- For exhaustive enums, the
// common file
package cmp
common enum A { ELEMENT1 | ELEMENT2 }
common enum B { ELEMENT1 | ELEMENT2 }
common enum C { ELEMENT1 | ELEMENT2 }
common enum D { ELEMENT1 | ELEMENT2 | ... }
common enum E { ELEMENT1 | ELEMENT2 | ... }
// specific file
package cmp
specific enum A { ELEMENT1 | ELEMENT2 } // ok
specific enum B { ELEMENT1 | ELEMENT2 | ELEMENT3 } // error: exhaustive enum cannot add new constructor
specific enum C { ELEMENT1 | ELEMENT2 | ... } // error: exhaustive 'common' enum cannot be matched with non-exhaustive 'specific' enum
specific enum D { ELEMENT1 | ELEMENT2 | ELEMENT3 } // ok
specific enum E { ELEMENT1 | ELEMENT2 | ELEMENT3 | ... } // ok
Enum Member Functions
common enums and specific enums must adhere to the following restrictions for member functions:
commonmember functions may have concrete implementations or only retain function signatures, with implementations provided byspecificmember functions.- If a
commonmember function has a complete implementation, the correspondingspecificmember function can be omitted; otherwise, a matchingspecificmember function must exist. - The parameters, return types, and modifiers (excluding
common/specific) ofcommonandspecificmember functions must be identical. - Both
commonandspecificenums support regular member functions, which can be defined in eithercommonorspecificenums.
// common file
package cmp
common enum A {
| ELEMENT
common func foo1(a: Int64): Unit
common func foo2(): Unit {}
common func foo3(): Unit {}
func foo4() {}
}
// specific file
package cmp
specific enum A {
| ELEMENT
specific func foo1(a: Int64): Unit { println(a) }
specific func foo3(): Unit { println("platform") }
func foo5(): Int64 { 1 }
}
Enum Properties
common enums and specific enums must adhere to the following restrictions for properties:
commonproperties may have concrete implementations or only retain property signatures, with implementations provided byspecificproperties.- If a
commonproperty has a complete implementation, the correspondingspecificproperty can be omitted; otherwise, a matchingspecificproperty must exist. - The types, visibility, and mutability of
commonandspecificproperties must be identical. - Both
commonandspecificenums support regular properties, which can be defined in eithercommonorspecificenums.
// common file
package cmp
common enum A {
| ELEMENT
common prop a: Int64
common prop b: Int64 {
get() { 1 }
}
common prop c: Int64 {
get() { 1 }
}
prop d: Int64 {
get() { 1 }
}
}
// specific file
package cmp
specific enum A {
| ELEMENT
specific prop a: Int64 {
get() { 1 }
}
specific prop c: Int64 {
get() { 2 }
}
prop e: Int64 {
get() { 1 }
}
}
Interface
Cangjie interfaces support cross-platform features. Users can use common and specific modifiers for interfaces and their members.
// common file
package cmp
common interface A {
common func foo(): Unit
common prop p: Int64
}
// specific file
package cmp
specific interface A {
specific func foo(): Unit {}
specific prop p: Int64 {
get() { 1 }
}
}
If a common interface exists, a matching specific interface must also exist, subject to the following requirements:
- The visibility of
commonandspecificinterfaces must be identical. - The interface implementation characteristics of
commonandspecificinterfaces must be identical. - A
commonsealed interface must match aspecificsealed interface. - Direct subtypes of a sealed interface must be defined in the same
commonpackage.
Interface Member Functions
common interfaces and specific interfaces must adhere to the following restrictions for member functions:
specificmember functions can be omitted regardless of whethercommonmember functions have complete implementations.- The parameters, return types, and modifiers (excluding
common/specific) ofcommonandspecificmember functions must be identical. - If a
commonmember function includes a concrete implementation, thespecificmember function must also include a concrete implementation. - Both
commonandspecificinterfaces support regular member functions, which can be defined in eithercommonorspecificinterfaces. - New regular functions added to
specificinterfaces must include complete implementations.
// common file
package cmp
common interface A {
common func foo1(a: Int64): Unit
common func foo2(): Unit
common func foo3(): Unit {}
func foo4(): Int64
}
// specific file
package cmp
specific interface A {
specific func foo1(a: Int64): Unit { println(a) }
specific func foo3(): Unit { println("platform") }
func foo5(): Int64 { 1 }
}
Interface Properties
common interfaces and specific interfaces must adhere to the following restrictions for properties:
specificproperties can be omitted regardless of whethercommonproperties have complete implementations.- The types, visibility, and mutability of
commonandspecificproperties must be identical. - If a
commonproperty includes a concrete implementation, thespecificproperty must also include a concrete implementation. - Both
commonandspecificinterfaces support regular properties, which can exist in eithercommonorspecificinterfaces. - New properties added to
specificinterfaces must include complete implementations.
// common file
package cmp
common interface A {
common prop a: Int64
common prop b: Int64
common prop c: Int64 {
get() { 1 }
}
prop d: Int64
}
// specific file
package cmp
specific interface A {
specific prop a: Int64 {
get() { 1 }
}
specific prop c: Int64 {
get() { 2 }
}
prop e: Int64 {
get() { 1 }
}
}
extend
Cangjie’s extend supports cross-platform features, allowing users to use common and specific modifiers for extend and its members.
Note:
Generic
extenddoes not currently support this feature.
// common file
package cmp
class A{}
common extend A {
common func foo(): Unit
common prop p: Int64
}
// specific file
package cmp
specific extend A {
specific func foo(): Unit {}
specific prop p: Int64 {
get() { 1 }
}
}
If there are one or more common extend declarations, there must be a unique matching specific extend, subject to the following requirements:
- When multiple
common extenddeclarations without interfaces exist, there must be exactly onespecific extend. It is prohibited to declare private functions with the same name across multiplecommon extenddeclarations. - When a
common extendwith declared interfaces exists, thecommon extendandspecific extendmust have identical interface sets.
Member Functions of extend
Member functions in common extend and specific extend must adhere to the following constraints:
- A
commonmember function may have a concrete implementation or only a function signature, with the implementation provided by thespecificmember function. - If a
commonmember function has a complete implementation, the correspondingspecificmember function may be omitted; otherwise, a matchingspecificmember function must exist. - Parameters, return types, and modifiers (excluding
common/specific) must be identical betweencommonandspecificmember functions. - Both
common extendandspecific extendsupport regular member functions, which can be defined in either.
// common file
package cmp
class A{}
common extend A {
common func foo1(a: Int64): Unit
common func foo2(): Unit { println("common") }
func foo3(): Unit{}
}
// specific file
package cmp
specific extend A {
specific func foo1(a: Int64): Unit { println(a) }
specific func foo2(): Unit { println("platform") }
func foo4(): Int64 { 1 }
}
Properties of extend
Properties in common extend and specific extend must adhere to the following constraints:
- A
commonproperty may have a concrete implementation or only a property signature, with the implementation provided by thespecificproperty. - If a
commonproperty has a complete implementation, the correspondingspecificproperty may be omitted; otherwise, a matchingspecificproperty must exist. - Property types, visibility, and mutability must be identical between
commonandspecificproperties. - Both
common extendandspecific extendsupport regular properties, which can be defined in either.
// common file
package cmp
class A{}
common extend A {
common prop a: Int64
common prop b: Int64 {
get() { 1 }
}
prop c: Int64{
get() { 1 }
}
}
// specific file
package cmp
specific extend A {
specific prop a: Int64 {
get() { 1 }
}
specific prop b: Int64 {
get() { 2 }
}
prop d: Int64 {
get() { 1 }
}
}
Cross-Platform Compilation
Users can compile cross-platform packages using cjc.
Note:
Import statements in the platform-specific code of a cross-platform package must be a superset of those in the common code; otherwise, compilation errors may occur.
Compilation with cjc
Given the following directory structure:
cjmp_project(package cjmp)
├── common
│ └── common.cj
├── platform
│ └── platform.cj
└── main.cj
-
First, compile the file containing the common code.
cjc --experimental common/common.cj --output-type=chir --output-dir ./common -
Next, compile the file containing the platform-specific code.
cjc --experimental platform/platform.cj common/common.chir --common-part-cjo=./common/cjmp.cjo --output-type=dylib --output-dir ./platform -
When invoking code for different platforms, specify the platform by referencing the
.sofile generated from compiling the platform-specific file.cjc main.cj -o main --import-path=./platform -L./platform -lcjmp
Cross-Platform Development Example
Using the Platform() Interface to Retrieve the Platform Name
Common definition file.
// common.cj
package example.cmp
// Retrieve platform information
public common func Platform(): String
Linux platform file.
// linux.cj
package example.cmp
public specific func Platform(): String {
"Linux"
}
Windows platform file.
// windows.cj
package example.cmp
public specific func Platform(): String {
"Win64"
}
macOS platform file.
// macos.cj
package example.cmp
public specific func Platform(): String {
"Mac"
}
Application-side code.
// app.cj
import example.cmp.Platform
main() {
println("${Platform()}")
}