Introduction
This is a (non-comprehensive) guide for JavaScript developers that are completely new to the C programming language. Some concepts and constructs translate fairly well between JavaScript and C, but which may be expressed differently, whereas others are a radical departure, like memory management. This guide provides a brief comparison and mapping of those constructs and concepts with concise examples.
The original authors1 of this guide were themselves JavaScript developers who were completely new to C. It is the guide the authors wish they had when they started on their C journey. That said, the authors would encourage you to read books and other material available on the Web to embrace C and its idioms rather than attempting to learn it exclusively through the lens of JavaScript. Meanwhile, this guide can help answers some question quickly, like: Does C support inheritance, threading, asynchronous programming, etc.? Assumptions:
- Reader is a seasoned JavaScript developer.
- Reader is completely new to C.
Goals:
- Provide a brief comparison and mapping of various JavaScript topics to their counterparts in C.
- Provide links to C reference, book and articles for further reading on topics.
Non-goals:
- Discussion of design patterns and architectures.
- Tutorial on the C language.
- Reader is proficient in C after reading this guide.
- While there are short examples that contrast JavaScript and C code for some topics, this guide is not meant to be a cookbook of coding recipes in the two languages.
The original authors of Microsoft's Rust for C#/.NET Developers were (in alphabetical order): Atif Aziz, Bastian Burger, Daniele Antonio Maggio, Dariusz Parys and Patrick Schuler.
The adaption work is done by @DevScholar on GitHub.
This book contains artificial intelligence generated content, and the content is audited.
License
MIT LicenseCopyright (c) Contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This book is adapted from Microsoft's Rust for C#/.NET Developers.
MIT LicenseCopyright (c) Microsoft Corporation. Portions Copyright (c) 2010 The Rust Project Developers
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Contributing
You are invited to contribute đź’– to this guide by opening issues and submitting pull requests!
Here are some ideas đź’ˇ for how and where you can help most with contributions:
-
Fix any spelling or grammatical mistakes you see as you read.
-
Fix technical inaccuracies.
-
Fix logical or compilation errors in code examples.
-
Improve the English, especially if it's your native tongue or you have excellent proficiency in the language.
-
Expand an explanation to provide more context or improve the clarity of some topic or concept.
-
Keep it fresh with changes in JavaScript and C. For example, if there is a change in JavaScript or C that brings the two languages closer together then some parts, including sample code, may need revision.
If you're making a small to modest correction, such fixing a spelling error or a syntax error in a code example, then feel free to submit a pull request directly. For changes that may require a large effort on your part (and reviewers as a result), it is strongly recommended that you submit an issue and seek approval of the maintainers/editors before investing your time. It will avoid heartbreak đź’” if the pull request is rejected for various reasons.
Making quick contributions has been made super simple. If you see an error on a page and happen to be online, you can click edit icon đź“ť in the corner of the page to edit the Markdown source of the content and submit a change.
Contribution Guidelines
-
Stick to the goals of this guide laid out in the introduction; put another way, avoid the non-goals!
-
Prefer to keep text short and use short, concise and realistic code examples to illustrate a point.
-
As much as it is possible, always provide and compare examples in C and JavaScript.
-
Feel free to use latest JavaScript/C language features if it makes an example simpler, concise and alike across the two languages.
-
Avoid using community packages in JavaScript examples. Stick to the JavaScript Global Objects as much as possible. Since the C Standard Library has a much smaller API surface, it is more acceptable to call out libraries for some functionality, should it be necessary for illustration (like
rand
for random number generation), but make sure they are mature, popular. -
Make example code as self-contained as possible and runnable (unless the idea is to illustrate a compile-time or run-time error).
-
Maintain the general style of this guide, which is to avoid using you as if the reader is being told or instructed; use the third-person voice instead. For example, instead of saying, “You represent optional data in C with the pointers”, write instead, “C has the pointers that is used to represent optional data”.
Getting Started
Online C Compilers
The easiest way to get started with C without needing any local installation is to use online C compilers. They are minimal development front-ends that runs in the Web browser and allows writing and running C code.
Dev Container
The execution environment of the online C compilers has some limitations, such as total compilation/execution time, memory and networking so another option that does not require installing C would be to use a dev container. Like online C compilers, the dev container can be run directly in a Web browser using GitHub Codespaces or locally using Visual Studio Code.
Local Install
For a complete local installation of C compiler and its development tools, see the installation page on websites of different compilers.
Language
This sections compares JavaScript and C language features.
Scalar Types
The following table lists the primitive types in C and their equivalent in JavaScript:
C | JavaScript | Note |
---|---|---|
bool (stdbool.h) | boolean | |
char | string | See note 1. |
char / signed char | number | See note 2. |
short int | number | |
int / signed int | number | |
long long int | number /bigint | |
unsigned long long int | number /bigint | |
LONG_LONG_MAX (limits.h) | Number.MAX_SAFE_INTEGER | |
unsigned char | number | |
unsigned short int | number | |
unsigned int | number | |
unsigned long long int | number /bigint | |
unsigned long long int | number /bigint | |
LONG_LONG_MAX (limits.h) | Number.MAX_SAFE_INTEGER | |
float | number /bigdecimal | |
double | number /bigdecimal | |
number | ||
null | null | |
undefined | ||
See note 3. |
Notes:
char
in C andstring
in JavaScript have different definitions. In C, achar
is 1 bytes wide, but in JavaScript, a character is 2 bytes wide and stores the character using the UTF-16 encoding. There is nochar
type equivalent in JavaScript, onlystring
. For more information, see the Cchar
documentation.- There are only three number data type in JavaScript,
number
, which is essentially a floating point number. And thebigint
type for storing numbers that exceed the range -(253 - 1) (Number.MIN_SAFE_INTEGER
) to 253 - 1 (Number.MAX_SAFE_INTEGER
). and thebigdecimal
type for storing high-precision decimals. - For historical reasons, JavaScript has two empty data types:
null
andundefined
.undefined
denotes a value that was never created, and null denotes a value that was created but intentionally left empty. See also:
Strings
Encoding issues with C language strings
In computers, characters are stored in binary encoding. A character set is a standard collection of encoded characters. In 1967, the American National Standards Institute (ANSI) released the ASCII character set that only supported the English alphabet, and then various countries and regions created various ASCII-compatible but incompatible character sets based on ASCII. These character sets are collectively known as multi-byte character sets (MBCS). In the 1990s, MBCS was the default standard for C character types and their associated functions. In 1991, the Unicode Consortium was formed to address the incompatibility of character sets, with the aim of creating a unified international character set standard. Initially, Unicode released the UTF-16 encoding standard, which is not ASCII compatible, and it has gradually and slowly gained support from the industry. Microsoft didn't release Windows 2000 with Unicode support until 2000.
However, on the Windows operating system platform, where compatibility is emphasized, the default standard for C character types and their associated functions is still the outdated MBCS (also known as narrow characters, functions end in A in the Windows API) rather than Unicode (at Microsoft, the word Unicode sometimes refers to UTF-16LE in code) (LE stands for Little Endian, which refers to the lower bytes first, The order in which the high-digit bytes are encoded after (also known as wide characters, functions end with W in the Windows API). For compatibility, Windows additionally defines the C character type 'wchar' and its associated functions for UTF-16LE strings, as well as the character type 'TCHAR' for both Unicode and non-Unicode compatibility (functions do not end with a capital A or W in the Windows API) and its related functions. This makes it difficult for Windows programmers. Later, more advanced UTF-8 encoding was released. Unix-like operating systems (e.g., Linux, FreeBSD), who is not very compatible, changed the default standard for C character types and their associated functions to UTF-8. Due to compatibility, and a lack of emphasis on UTF-8, Windows does not support UTF-8 as a standard for encoding programs internally. Windows programmers are still miserable.
The turning point came in 2015, when Microsoft's new CEO, Satya Nadella, took office, and he launched a strategy to embrace open source software. In the open-source world, people generally use Linux. At this point, character encoding becomes a big problem. So, in 2019, starting with Windows version 1903 (May 2019 Update), you can use manifests to have processes use UTF-8 as process code pages. In order to avoid conflicts with UTF-16LE, Microsoft has modified the MBCS related APIs to do this. But for compatibility reasons, the preferred program internal encoding on Windows is still UTF-16LE. In addition, Microsoft began to develop a terminal "Windows Terminal" that supports Unicode (including UTF-16LE, UTF-8, etc.) to solve this problem. Previously, Windows Terminal did not support Unicode characters. Now, Windows programmers can finally use UTF-8 as the default standard for C character types and their associated functions, as well as for the internal encoding of programs, by adding a manifest.
There is a character type in C: char
.
The mapping of those to JavaScript is shown in the following table:
C | JavaScript | Note |
---|---|---|
char | string | see Note 1. |
There are differences in working with strings in C and JavaScript, but the equivalents above should be a good starting point. One of the differences is that C characters are using different encodes, but JavaScript strings are UTF-16 encoded.
Notes:
- JavaScript has only one string type,
string
. JavaScript has no pointer types.
JavaScript:
let str = "Hello, World!";
let str = new String("Hello, World!")
C:
char str[]= "Hello, World!";
String Literals
String literals in JavaScript String
types and allocated on the heap. In Rust, they are char
, has a global lifetime and does not get allocated on the heap; they're embedded in the compiled binary.
JavaScript:
let str = "Hello, World!";
C:
char str[]= "Hello, World!"
JavaScript verbatim string literals are equivalent to Rust raw string literals.
JavaScript
let str = `Hello, \World/!`;
C
char *str = R"(Hello, \World/!)";
C UTF-8 string literals:
char str[] = u8"hello";
String Interpolation
JavaScript has a built-in string interpolation feature that allows you to embed expressions inside a string literal. The following example shows how to use string interpolation in JavaScript:
let name = "John";
let age = 42;
let str = `Person Name: ${name}, Age: ${age} `;
C does not have a built-in string interpolation feature. Instead, the printf
function is used to format a string. The following example shows how to use string interpolation in C:
#include <stdio.h>
int main() {
char name[] = "John";
int age = 42;
printf("Person Name: %s, Age: %d", name, age);
return 0;
}
Custom classes and structs can also be interpolated in JavaScript due to the fact that the toString()
method is available for each type as it inherits from object
.
class Person {
constructor({
name,
age
}) {
this.name = name;
this.age = age;
this.toString = function() {
return `Person Name: ${name}, Age: ${age}`;
}
}
}
let person = new Person({
name: "John",
age: 42
});
console.log(person);
In C, there is no default formatting implemented/inherited for each type. We use structs instead of classes, and function pointers to simulate methods in classes.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char *name;
int age;
char* (*toString)(void);
} Person;
char* Person_toString() {
return "Person Name: %s, Age: %d";
}
Person* new_Person(char* name, int age) {
Person* person = (Person*)malloc(sizeof(Person));
person->name = name;
person->age = age;
person->toString = Person_toString;
return person;
}
int main() {
Person* person = new_Person("John", 42);
printf(person->toString(), person->name, person->age);
free(person);
return 0;
}
Text Replacing
C doesn't have a built-in string replacement feature, so replacing strings is very complex in C. Here's the code for a C function to replace a string:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/**
* @brief Replace a substring in a string with another substring.
* @param str The original string.
* @param find The substring to find.
* @param replace The substring to replace.
* @param case_sensitive Flag to indicate case sensitivity (1 for case-sensitive, 0 for case-insensitive).
* @param global_replace Flag to enable global replacement (1 for global, 0 for first occurrence only).
* @return The modified string after replacement.
*/
char* replace_str(const char* str, const char* find, const char* replace, int case_sensitive, int global_replace) {
char* result;
int i, count = 0;
int find_len = strlen(find);
int replace_len = strlen(replace);
for (i = 0; str[i] != '\0';) {
if ((case_sensitive ? strncmp(&str[i], find, find_len) : strncasecmp(&str[i], find, find_len)) == 0) {
count++;
if (!global_replace) break;
i += find_len;
} else {
i++;
}
}
result = (char*)malloc(strlen(str) + count * (replace_len - find_len) + 1);
i = 0;
while (*str) {
if ((case_sensitive ? strncmp(str, find, find_len) : strncasecmp(str, find, find_len)) == 0) {
strcpy(&result[i], replace);
i += replace_len;
str += find_len;
if (!global_replace) break;
} else {
result[i++] = *str++;
}
}
result[i] = '\0';
return result;
}
int main() {
const char* original_str = "Hello, World!";
const char* find_str = "World";
const char* replacement_str = "C Language";
char* new_str = replace_str(original_str, find_str, replacement_str, 0, 1); // Case-insensitive, global replacement
printf("Original String: %s\n", original_str);
printf("New String: %s\n", new_str);
free(new_str);
return 0;
}
Structured Types
Commonly used object and collection types in JavaScript and their mapping to C.
JavaScript | C |
---|---|
array | (The type of an array varies with types) |
Array
Fixed arrays are supported the same way in C as in JavaScript.
JavaScript:
let someArray = [1,2];
C:
int someArray[2] = {1, 2};
List
In C the equivalent of a array
is an array.
JavaScript:
let something = ["a", "b"];
something.push("c");
c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char **something = malloc(2 * sizeof(char*));
something[0] = "a";
something[1] = "b";
int size = 2;
// Before adding an element
for (int i = 0; i < size; i++) {
printf("%s ", something[i]);
}
// Adding an element
char *new_element = "c";
size++;
char **temp = realloc(something, size * sizeof(char*));
if (temp != NULL) {
something = temp;
something[size - 1] = new_element;
}
// After adding an element
printf("\n");
for (int i = 0; i < size; i++) {
printf("%s ", something[i]);
}
// Release the memory.
free(something);
return 0;
}
Tuples
JavaScript:
const let something = [1, 2];
console.log(`a = ${something[0]} b = ${something[1]}`);
In C, arrays are used to simulate the concept of tuples:
#include <stdio.h>
int main() {
int something[] = {1, 2};
printf("a = %d b = %d", something[0], something[1]);
return 0;
}
// deconstruction supported
#include <stdio.h>
int main() {
// Define a tuple
struct Tuple {
int a;
int b;
};
// Initialize the tuple
struct Tuple something = {1, 2};
// Deconstruct tuples
int a = something.a;
int b = something.b;
printf("a = %d b = %d\n", a, b);
return 0;
}
NOTE: C tuple elements cannot be named. The only way to access a tuple element is by using the index of the element or deconstructing the tuple.
Dictionary
In C the equivalent of a Dictionary
is a object
.
JavaScript:
var something = {
"Foo": "Bar",
"Baz": "Qux"
};
something["hi"] = "there";
C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INITIAL_CAPACITY 2
typedef struct {
char* key;
char* value;
} Entry;
typedef struct {
Entry* entries;
int capacity;
int size;
} HashMap;
void insert(HashMap* map, char* key, char* value) {
if (map->size >= map->capacity) {
map->capacity *= 2;
map->entries = realloc(map->entries, map->capacity * sizeof(Entry));
}
map->entries[map->size].key = strdup(key);
map->entries[map->size].value = strdup(value);
map->size++;
}
void printHashMap(HashMap* map) {
printf("HashMap Contents:\n");
for (int i = 0; i < map->size; i++) {
printf("Key: %s, Value: %s\n", map->entries[i].key, map->entries[i].value);
}
}
int main() {
HashMap something;
something.entries = malloc(INITIAL_CAPACITY * sizeof(Entry));
something.capacity = INITIAL_CAPACITY;
something.size = 0;
insert(&something, "Foo", "Bar");
insert(&something, "Baz", "Qux");
insert(&something, "hi", "there");
// Add any other actions here or print the code for the HashMap
printHashMap(&something);
return 0;
}
See also:
Custom Types
The following sections discuss various topics and constructs related to developing custom types:
Classes
C doesn't have classes. It only has struct
.
Records
C doesn't have any construct for authoring records.
Structures (struct
)
In JavaScript, there is no direct concept of a structure, but you can use objects to model similar structures. Structures in C:
-
In C,
struct
simply defines the data/fields. Developers can encapsulate data by defining structs and manipulating that data with functions, which is similar to impl in rust. -
They cannot be sub-classed.
-
They are allocated on stack by default.
In C, a struct
is the primary construct for modeling any data structure (the other being an enum
).
In C, structs do not need to implement traits, as C does not support the concept of traits in object-oriented programming.
#include <stdio.h>
struct Point {
int x;
int y;
};
int main() {
struct Point p;
p.x = 10;
p.y = 20;
printf("Point coordinates: (%d, %d)\n", p.x, p.y);
return 0;
}
Value types in JavaScript are usually designed by a developer to be mutable. It's considered best practice speaking semantically, but the language does not prevent designing a struct
that makes destructive or in-place modifications.
Since C doesn't have classes and consequently type hierarchies based on sub-classing, shared behaviour is achieved via traits and generics and polymorphism via virtual dispatch using trait objects.
In JavaScript:
class Rectangle {
constructor(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
length() {
return this.y2 - this.y1;
}
width() {
return this.x2 - this.x1;
}
top_left() {
return [this.x1, this.y1];
}
bottom_right() {
return [this.x2, this.y2];
}
area() {
return this.length() * this.width();
}
is_square() {
return this.width() === this.length();
}
toString() {
return `(${this.x1}, ${this.y1}), (${this.x2}, ${this.y2})`;
}
}
const rect = new Rectangle(0, 0, 4, 4);
console.log(rect.area());
console.log(rect.toString());
The equivalent in C would be:
#include <stdio.h>
typedef struct {
int x1, y1, x2, y2;
} Rectangle;
int length(Rectangle rect) {
return rect.y2 - rect.y1;
}
int width(Rectangle rect) {
return rect.x2 - rect.x1;
}
int area(Rectangle rect) {
return length(rect) * width(rect);
}
int is_square(Rectangle rect) {
return width(rect) == length(rect);
}
void display(Rectangle rect) {
printf("(%d, %d), (%d, %d)\n", rect.x1, rect.y1, rect.x2, rect.y2);
}
int main() {
Rectangle rect = {0, 0, 4, 4};
printf("Length: %d\n", length(rect));
printf("Width: %d\n", width(rect));
printf("Area: %d\n", area(rect));
printf("Is Square: %s\n", is_square(rect) ? "Yes" : "No");
display(rect);
return 0;
}
Since there is no inheritance in C, the way a type advertises support for some formatted representation is by defining a struct for Rectangle and a function to print its contents.:
#include <stdio.h>
typedef struct {
int x;
int y;
int width;
int height;
} Rectangle;
void displayRectangle(Rectangle rect) {
printf("Rectangle = { x: %d, y: %d, width: %d, height: %d }\n", rect.x, rect.y, rect.width, rect.height);
}
int main() {
Rectangle rect = {12, 34, 56, 78};
displayRectangle(rect);
return 0;
}
Interfaces
When creating an interface in C, developers often rely on structures to define the interface's layout. By encapsulating related data fields and function pointers within a structure, C programmers can simulate an interface-like behavior.
Here's a simple example to illustrate how an interface can be introduced in C:
#include <stdio.h>
// Define the interface structure
struct Interface {
int (*getData)(); // Function pointer for getting data
void (*displayData)(); // Function pointer for displaying data
};
// Implement functions for the interface
int getDataImplementation() {
return 42;
}
void displayDataImplementation() {
printf("Data: %d\n", getDataImplementation());
}
int main() {
// Create an instance of the interface
struct Interface myInterface = {getDataImplementation, displayDataImplementation};
// Utilize the interface functions
myInterface.displayData();
return 0;
}
In this example, the Interface structure defines the layout of the interface with function pointers for getData and displayData. The getDataImplementation and displayDataImplementation functions serve as the concrete implementations of these interface functions.
By instantiating the Interface structure and assigning the appropriate function implementations, developers can achieve a form of interface-like behavior in C. This approach allows for abstraction and modularity in C programming, enabling better code organization and reusability.
Enumeration types (enum
)
To convert a C enum to JavaScript, you can represent it using an object with key-value pairs.
const DayOfWeek = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6
};
C syntax for doing the same:
enum DayOfWeek
{
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6,
};
When dealing with an enum type in C, there is no inherent behavior or predefined functionality associated with instances of the enum.
In C, enums do not have built-in support for coercion to integral values. Developers need to manually implement functions to achieve this behavior. Here is an example of how to achieve similar functionality in C:
#include <stdio.h>
typedef enum {
Sunday = 0,
Monday = 1,
Tuesday = 2,
Wednesday = 3,
Thursday = 4,
Friday = 5,
Saturday = 6
} DayOfWeek;
int main() {
DayOfWeek dow = Wednesday;
printf("Day of week = %d\n", dow);
if (dow == Friday) {
printf("Yay! It's the weekend!\n");
}
// Coerce to integer
int dowInt = (int)dow;
printf("Day of week = %d\n", dowInt);
// Manually coerce back to DayOfWeek
DayOfWeek newDow = (DayOfWeek)dowInt;
printf("Day of week = %d\n", newDow);
return 0;
}
In C, enums can be casted manually to integral types and vice versa. This approach gives developers control over the conversion process, similar to the Rust example provided.
In C, to convert an integral type to an enum, one can create a function that maps integral values to an enum:
#include <stdio.h>
typedef enum {
Sunday = 0,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
} DayOfWeek;
DayOfWeek try_from_i32(int n) {
if (n >= 0 && n <= 6) {
return (DayOfWeek)n;
} else {
return -1; // Or any other error code indicating failure
}
}
int main() {
DayOfWeek dow = try_from_i32(5);
if (dow != -1) {
printf("Day of the week: %d\n", dow);
} else {
printf("Error: Invalid day\n");
}
dow = try_from_i32(50);
if (dow != -1) {
printf("Day of the week: %d\n", dow);
} else {
printf("Error: Invalid day\n");
}
return 0;
}
In this C code snippet, the try_from_i32 function maps the integer input to the corresponding DayOfWeek enum value if valid. It handles out-of-range values by returning an error code.
In C, enum types can be utilized to create union types, allowing for different variants to store variant-specific data. This concept is similar to Rust's discriminated union types. Below is an example illustrating the use of enum for union types in C:
#include <stdio.h>
#include <stdint.h>
#include <string.h>
enum IpAddrTag {
V4,
V6
};
struct IpAddrV4 {
uint8_t octet1;
uint8_t octet2;
uint8_t octet3;
uint8_t octet4;
};
struct IpAddrV6 {
char address[40]; // Assuming a maximum IPv6 address length
};
union IpAddr {
struct IpAddrV4 v4;
struct IpAddrV6 v6;
};
int main() {
union IpAddr home;
home.v4.octet1 = 127;
home.v4.octet2 = 0;
home.v4.octet3 = 0;
home.v4.octet4 = 1;
union IpAddr loopback;
strcpy(loopback.v6.address, "::1");
return 0;
}
This form of enum
declaration does not exist in JavaScript, but it can be emulated with classes:
class IpAddr {
constructor(v4, v6) {
this.v4 = v4;
this.v6 = v6;
}
}
const home = new IpAddr([127, 0, 0, 1], null);
const loopback = new IpAddr(null, "::1");
Members
Constructors
In C programming, constructors are not explicitly defined. Instead, you can achieve similar functionality by using factory functions that create and initialize instances of a struct. These factory functions can be standalone functions or associated functions of the struct. Conventionally, if there is only one factory function for a struct, it is commonly named new.
#include <stdio.h>
typedef struct {
int x1, y1, x2, y2;
} Rectangle;
Rectangle newRectangle(int x1, int y1, int x2, int y2) {
Rectangle rect;
rect.x1 = x1;
rect.y1 = y1;
rect.x2 = x2;
rect.y2 = y2;
return rect;
}
int main() {
Rectangle myRect = newRectangle(0, 0, 100, 100);
printf("Rectangle coordinates: (%d, %d), (%d, %d)\n", myRect.x1, myRect.y1, myRect.x2, myRect.y2);
return 0;
}
Methods (static & instance-based)
C types (both enum
and struct
), can have static and instance-based methods.
#include <stdio.h>
typedef struct {
int x1, y1, x2, y2;
} Rectangle;
Rectangle new_rectangle(int x1, int y1, int x2, int y2) {
Rectangle rect;
rect.x1 = x1;
rect.y1 = y1;
rect.x2 = x2;
rect.y2 = y2;
return rect;
}
int length(Rectangle *rect) {
return rect->y2 - rect->y1;
}
int width(Rectangle *rect) {
return rect->x2 - rect->x1;
}
int area(Rectangle *rect) {
return length(rect) * width(rect);
}
int main() {
Rectangle rect = new_rectangle(0, 0, 4, 3);
printf("Length: %d\n", length(&rect));
printf("Width: %d\n", width(&rect));
printf("Area: %d\n", area(&rect));
return 0;
}
Constants
A type in C can have constants. However, the most interesting aspect to note is that C allows a type instance to be defined as a constant too:
struct Point {
int x;
int y;
};
const struct Point ZERO = {0, 0};
In JavaScript, you need to define a Point class, and then, simulate the construction behavior in Rust by setting a static property ZERO directly on the Point class.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
Point.ZERO = new Point(0, 0);
Events
C has no built-in support for type members to adverstise and fire events.
Properties
In C, there are no built-in properties. To mimic property-like behavior, the user can use getter and setter functions.
#include <stdio.h>
typedef struct {
int x1, y1, x2, y2;
} Rectangle;
Rectangle newRectangle(int x1, int y1, int x2, int y2) {
Rectangle rect = {x1, y1, x2, y2};
return rect;
}
// Getter functions
int getX1(Rectangle *rect) { return rect->x1; }
int getY1(Rectangle *rect) { return rect->y1; }
int getX2(Rectangle *rect) { return rect->x2; }
int getY2(Rectangle *rect) { return rect->y2; }
// Setter functions
void setX1(Rectangle *rect, int val) { rect->x1 = val; }
void setY1(Rectangle *rect, int val) { rect->y1 = val; }
void setX2(Rectangle *rect, int val) { rect->x2 = val; }
void setY2(Rectangle *rect, int val) { rect->y2 = val; }
// Computed properties
int length(Rectangle *rect) { return rect->y2 - rect->y1; }
int width(Rectangle *rect) { return rect->x2 - rect->x1; }
int area(Rectangle *rect) { return length(rect) * width(rect); }
int main() {
Rectangle rect = newRectangle(0, 0, 10, 20);
printf("Area of the rectangle: %d\n", area(&rect));
return 0;
}
Extension Methods
In JavaScript, you can use prototype to add new methods to existing classes. This approach allows you to add new behavior to an existing class without changing the existing class definition:
//JavaScript doesn't have a StringBuilder class. This code is only used to demonstrate adding a new method to an existing class.
class StringBuilder {
constructor(initialString) {
this.value = initialString;
}
toString() {
return this.value;
}
}
StringBuilder.prototype.wrap = function (left, right) {
this.value = left + this.value + right;
}
const sb = new StringBuilder("Hello, World!");
sb.wrap(">>> ", " <<<");
console.log(sb.toString());
C does not have built-in support for extension methods or traits. To achieve similar functionality in C, one can use function pointers or structures to mimic extension methods. Here's a simplified example in C to demonstrate extending a type with a method:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Define the StringExt structure before using it.
typedef struct StringExt {
char* data;
// The wrap function should take a pointer to StringExt to modify the original structure.
void (*wrap)(struct StringExt*, const char*, const char*);
} StringExt;
// The wrap function now correctly takes a pointer to a StringExt structure.
void wrap(struct StringExt* self, const char* left, const char* right) {
if (self == NULL || self->data == NULL || left == NULL || right == NULL) {
return; // Safety check to avoid dereferencing NULL pointers.
}
// Allocate memory for the new string, including space for the null terminator.
char* temp = malloc(strlen(left) + strlen(self->data) + strlen(right) + 1);
if (temp == NULL) {
return; // Check for failed memory allocation.
}
// Concatenate the strings in the correct order.
strcpy(temp, left);
strcat(temp, self->data);
strcat(temp, right);
// Free the old data and update the StringExt structure with the new data.
free(self->data);
self->data = temp;
}
int main() {
// Declare the StringExt variable as an actual structure, not just a type.
StringExt s = { .data = strdup("Hello, World!"), .wrap = wrap };
// Call the wrap function with the address of the StringExt structure.
s.wrap(&s, ">>> ", " <<<");
printf("%s\n", s.data); // Should now print: >>> Hello, World! <<<
free(s.data); // Free the allocated memory for the data string.
return 0;
}
Visibility/Access modifiers
In JavaScript, there is no explicit visibility modifiers, but similar functionality can be achieved with some conventions.
In C, visibility and access control are primarily achieved through the use of header files and the concept of translation units. By declaring functions and variables in header files and including them in source files, C provides a way to control visibility. To mimic private members, one can use static variables or functions within a source file, limiting their scope to that file. For public visibility, declaring functions and variables in header files and including those headers in multiple source files allows for shared access. While C lacks explicit modifiers like pub in Rust, the structuring of code using header files and source files provides a similar level of control over visibility and access.
Mutability
When designing a type in JavaScript, it is not the responsiblity of the developer to decide whether the a type is mutable or immutable; whether it supports destructive or non-destructive mutations. In C, mutability is expressed on methods through the type of the parameters as shown in the example below:
#include <stdio.h>
struct Point {
int x;
int y;
};
struct Point new_point(int x, int y) {
struct Point p;
p.x = x;
p.y = y;
return p;
}
int get_x(struct Point *p) {
return p->x;
}
int get_y(struct Point *p) {
return p->y;
}
void set_x(struct Point *p, int val) {
p->x = val;
}
void set_y(struct Point *p, int val) {
p->y = val;
}
int main() {
struct Point p = new_point(3, 4);
printf("Point coordinates: (%d, %d)\n", get_x(&p), get_y(&p));
set_x(&p, 7);
set_y(&p, 8);
printf("Updated point coordinates: (%d, %d)\n", get_x(&p), get_y(&p));
return 0;
}
In JavaScript, use ES6's destructuring assignment and object extension syntax to implement non-destructive mutation:
class Point {
constructor(X, Y) {
this.X = X;
this.Y = Y;
}
}
let pt = new Point(123, 456);
pt = { ...pt, X: 789 };
console.log(pt); // prints: Point { X = 789, Y = 456 }
There is no with
in C, but to emulate something similar in C, it has to be baked into the type's design:
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
Point new_point(int x, int y) {
Point p;
p.x = x;
p.y = y;
return p;
}
int get_x(Point p) {
return p.x;
}
int get_y(Point p) {
return p.y;
}
Point set_x(Point p, int val) {
p.x = val;
return p;
}
Point set_y(Point p, int val) {
p.y = val;
return p;
}
int main() {
Point p = new_point(3, 4);
printf("Initial Point: x=%d, y=%d\n", get_x(p), get_y(p));
p = set_x(p, 7);
p = set_y(p, 9);
printf("Modified Point: x=%d, y=%d\n", get_x(p), get_y(p));
return 0;
}
In JavaScript, classes are used to simulate structs, and destructuring objects is assigned to implement something like with
.
class Point {
constructor(x, y) {
this.X = x;
this.Y = y;
}
toString() {
return `(${this.X}, ${this.Y})`;
}
}
let pt = new Point(123, 456);
console.log(pt.toString()); // prints: (123, 456)
pt = { ...pt, X: 789 };
console.log(pt.toString()); // prints: (789, 456)
C has another syntax that may seem similar:
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
Point createPoint(int x, int y) {
Point pt;
pt.x = x;
pt.y = y;
return pt;
}
void printPoint(Point pt) {
printf("Point { x: %d, y: %d }\n", pt.x, pt.y);
}
Point set_x(Point p, int val) {
p.x = val;
return p;
}
Point set_y(Point p, int val) {
p.y = val;
return p;
}
int get_x(Point p) {
return p.x;
}
int get_y(Point p) {
return p.y;
}
int main() {
Point p = createPoint(3, 4);
printf("Initial Point: x=%d, y=%d\n", get_x(p), get_y(p));
p = set_x(p, 7);
p = set_y(p, 9);
printf("Modified Point: x=%d, y=%d\n", get_x(p), get_y(p));
return 0;
}
Local Functions
C offer local functions,but C functions cannot use variables from their surrounding lexical scope.
Lambda and Closures
C does not directly support higher-order functions. However, function pointers in C can be used to achieve similar behavior.
#include <stdio.h>
typedef int (*func_ptr)(int);
int do_twice(func_ptr f, int arg) {
return f(arg) + f(arg);
}
int add_one(int x) {
return x + 1;
}
int main() {
int answer = do_twice(add_one, 5);
printf("The answer is: %d\n", answer); // Prints: The answer is: 12
return 0;
}
In JavaScript:
function doTwice(f, arg) {
return f(arg) + f(arg);
}
function main() {
const answer = doTwice(x => x + 1, 5);
console.log(`The answer is: ${answer}`); // Prints: The answer is: 12
}
main();
In C programming, closures can be simulated using function pointers and structures. By encapsulating data and functions within a structure, C can achieve a form of closures. This approach allows functions to access variables from their enclosing scope even after the scope has exited. The structure holds the data and function pointers, enabling the functions to operate on the enclosed data. While C does not have native closure support like some other languages, this workaround provides a mechanism for achieving similar functionality. By leveraging function pointers and structures, C programmers can implement closures effectively within their code.
Variables
Consider the following example around variable assignment in JavaScript:
let x = 5;
And the same in C:
int x = 5;
C is not type-safe: the compiler guarantees that the value stored in a variable is always of the designated type. The example can be simplified by using the compiler's ability to automatically infer the types of the variable. In JavaScript:
let x = 5;
In C:
auto x = 5;
When expanding the first example to update the value of the variable (reassignment), the behavior of JavaScript and Rust differ:
let x = 5;
x = 6;
console.log(x); // 6
In C, the identical statement will compile:
#include <stdio.h>
int main() {
int x = 5;
x = 6; // Variable 'x' is mutable in C
printf("%d", x);
return 0;
}
In C, variables are mutable by default. Once a value is bound to a name, the variable's value can be changed:
#include <stdio.h>
int main() {
int x = 5;
x = 6;
printf("%d", x);
return 0;
}
In C, the concept of variable shadowing is not directly supported. However, a similar effect can be achieved by declaring a new variable with the same name in a nested scope:
#include <stdio.h>
int main() {
int x = 5;
{
int x = 6;
printf("%d", x); // Output: 6
}
return 0;
}
JavaScript also supports shadowing, e.g. locals can shadow fields and type members can shadow members from the base type. In Rust, the above example demonstrates that shadowing also allows to change the type of a variable without changing the name, which is useful if one wants to transform the data into different types and shapes without having to come up with a distinct name each time.
See also:
- Data races and race conditions for more information around the implications of mutability
- [Scope and shadowing]
- Memory management for explanations
Namespaces
In JavaScript, there is no such thing as a namespace. Instead, JavaScript developers use sub-objects or conventions to implement namespace-like functionality.
In C, there is no direct support for namespaces like in C++. However, it is possible to achieve a similar effect by using a naming convention to simulate namespaces. One common approach is to prefix your functions and variables with a unique identifier to mimic the behavior of namespaces.
Equality
When comparing for equality in JavaScript, this refers to testing for equivalence insome cases (also known as value equality), and in other cases it refers to testing for reference equality, which tests whether two variables refer to the same underlying object in memory. In JavaScript, while there is no syntax for explicitly custom types, custom types can be simulated through constructors and prototypes. Constructors allow you to create objects with specific properties and methods, and you can use prototypes to implement inheritance and shared methods. Every "custom type" can be compared for equality because it inherits from object
.
For example, when comparing for equivalence and reference equality in JavaScript:
class Point {
constructor(X, Y) {
this.X = X;
this.Y = Y;
}
equals(other) {
return this.X === other.X && this.Y === other.Y;
}
}
const a = new Point(1, 2);
const b = new Point(1, 2);
const c = a;
console.log(a.equals(b)); // (1) true
console.log(a.equals(new Point(2, 2))); // (1) false
console.log(a === b); // (2) false
console.log(a === c); // (2) true
- In JavaScript, classes are used to implement equals methods to compare the equality of values.
- For the comparison of reference equality, using the === operator to check if the variable points to the same object in memory.
Equivalently in C:
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
int point_equals(Point a, Point b) {
return a.x == b.x && a.y == b.y;
}
int main() {
Point a = {1, 2};
Point b = {1, 2};
Point c = a;
printf("%d\n", point_equals(a, b)); // true
printf("%d\n", point_equals(a, b)); // true
printf("%d\n", point_equals(a, (Point){2, 2})); // false
printf("%d\n", point_equals(a, c)); // true
return 0;
}
Generics
Generics provide a way to create definitions for types and methods that can be parameterized over other types. This improves code reuse, type-safety and performance (e.g. avoid run-time casts). Consider the following example of a generic type that adds a timestamp to any value. However, JavaScript does not have the concept of generics.
class Timestamped {
constructor(value) {
this.Timestamp = new Date();
this.Value = value;
}
}
C does not have built-in support for generics. In C, preprocessor macros or void pointers are typically used to achieve a similar effect.
Here's a simplified example in C that mimics the Rust code using a void pointer to achieve a generic-like behavior:
#include <stdio.h>
#include <time.h>
typedef struct {
void* value;
time_t timestamp;
} Timestamped;
Timestamped new(void* value) {
Timestamped ts;
ts.value = value;
ts.timestamp = time(NULL);
return ts;
}
int main() {
int intValue = 42;
Timestamped intTimestamped = new(&intValue);
char charValue = 'A';
Timestamped charTimestamped = new(&charValue);
return 0;
}
Generic type constraints
JavaScript has no concept of generics, and it is a weakly typed scripting language that makes it impossible to add type constraints to it.
class Timestamped {
constructor(value) {
this.value = value;
this.timestamp = Date.now();
}
equals(other) {
return this.value === other.value && this.timestamp === other.timestamp;
}
}
The same can be achieved in C:
#include <stdio.h>
#include <time.h>
typedef struct {
void* value;
time_t timestamp;
} Timestamped;
Timestamped new(void* value) {
Timestamped ts;
ts.value = value;
ts.timestamp = time(NULL);
return ts;
}
int equal(Timestamped* ts1, Timestamped* ts2) {
return ts1->value == ts2->value && ts1->timestamp == ts2->timestamp;
}
int main() {
void* val = NULL;
Timestamped ts1 = new(val);
Timestamped ts2 = new(val);
if (equal(&ts1, &ts2)) {
printf("Timestamped values are equal.\n");
} else {
printf("Timestamped values are not equal.\n");
}
return 0;
}
Polymorphism
C does not support classes and sub-classing therefore polymorphism can't be achieved in an identical manner to JavaScript.
See also:
- Virtual dispatch using trait objects, as explained in the Structures section
- Generics
- Inheritance
- Operator overloading
Inheritance
As explained in structures section, C does not provide (class-based) inheritance. In C, there is no direct support for traits or supertraits. To achieve similar functionality in C, one can use structures and function pointers. By defining a structure that holds function pointers to shared behaviors and then implementing those behaviors in separate functions, one can simulate trait-like behavior in C. This approach allows for defining relationships between different structs by sharing common behavior through function pointers within the structs.
Exception Handling
In JavaScript, an exception should always be an Error
object or an instance of an Error
subclass. Exceptions are thrown if a problem occurs in a code section. A thrown exception is passed up the stack until the application handles it or the program terminates.
In C, error handling is typically done using return values or error codes. To convert the Rust code snippet provided to C, one would need to implement error handling using return values or custom error structures.
Custom error types
An example on how to create user-defined exceptions:
class EmployeeListNotFoundException extends Error {
constructor(message) {
super(message);
this.name = 'EmployeeListNotFoundException';
}
}
In C, one can achieve similar error handling by defining custom error structures and functions.
#include <stdio.h>
#include <stdlib.h>
typedef struct {
const char* message;
} EmployeeListNotFound;
const char* EmployeeListNotFound_message(EmployeeListNotFound* err) {
return err->message;
}
void EmployeeListNotFound_free(EmployeeListNotFound* err) {
free(err);
}
int main() {
EmployeeListNotFound* err = (EmployeeListNotFound*)malloc(sizeof(EmployeeListNotFound));
err->message = "Could not find employee list.";
// Example usage
printf("%s\n", EmployeeListNotFound_message(err));
EmployeeListNotFound_free(err);
return 0;
}
In C, the equivalent of the JavaScript Error.cause
property can be achieved by defining a custom error structure that includes a field to store the error cause or source. Here is a simplified example:
#include <stdio.h>
typedef struct {
const char* cause; // Field to store the error cause/source
} Error;
int main() {
Error customError;
customError.cause = "Custom error message";
printf("Error Cause: %s\n", customError.cause);
return 0;
}
Raising exceptions
To raise an error in JavaScript, throw an error:
function throwIfNegative(value) {
if (value < 0) {
throw new Error('Value cannot be negative');
}
}
To mimic recoverable errors in C, an enum is used to define custom result types. The function error_if_negative checks if the input value is negative and returns the appropriate result. The main function demonstrates how to use this error handling mechanism in C.
#include <stdio.h>
typedef enum {
OK,
ERR
} Result;
Result error_if_negative(int value) {
if (value < 0) {
return ERR;
} else {
return OK;
}
}
int main() {
int input = -5;
Result result = error_if_negative(input);
if (result == ERR) {
printf("Error: Specified argument was out of the range of valid values. (Parameter 'value')\n");
} else {
printf("No error. Value is non-negative.\n");
}
return 0;
}
In C, a way to handle unrecoverable errors is typically achieved using the abort() function from the <stdlib.h>
library.
#include <stdio.h>
#include <stdlib.h>
void panic_if_negative(int value) {
if (value < 0) {
fprintf(stderr, "Specified argument was out of the range of valid values. (Parameter 'value')\n");
abort();
}
}
int main() {
//Main code logic here
return 0;
}
Error propagation
In JavaScript, exceptions are passed up until they are handled or the program terminates. In Rust, unrecoverable errors behave similarly, but handling them is uncommon.
Recoverable errors, however, need to be propagated and handled explicitly. Their presence is always indicated by the Rust function or method signature. Catching an exception allows you to take action based on the presence or absence of an error in JavaScript:
//JavaScript doesn't have a file system in it. People often implement file systems using the BrowserFS library that mimic Node.js APIs.
function write() {
try {
fs.writeFileSync('file.txt', 'content');
} catch (error) {
console.log('Writing to file failed.');
}
}
In C, error handling is typically done using return values rather than exceptions. One would need to use functions that return error codes to indicate success or failure instead of throwing exceptions.
#include <stdio.h>
int write() {
FILE *file = fopen("file.txt", "w");
if (file == NULL) {
printf("Error opening file.\n");
return -1; // Return an error code
}
if (fprintf(file, "content") < 0) {
printf("Error writing to file.\n");
fclose(file);
return -2; // Return a different error code
}
fclose(file);
return 0; // Return 0 for success
}
int main() {
int result = write();
if (result != 0) {
printf("Writing to file failed.\n");
}
return 0;
}
C does not have a built-in operator like Rust's ? for error propagation. In C, error handling is typically done using return values or error codes.
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file = fopen("file.txt", "w");
if (file == NULL) {
perror("Error opening file");
return -1; // Error code for file opening failure
}
if (fwrite("content", sizeof(char), 7, file) != 7) {
perror("Error writing to file");
fclose(file);
return -2; // Error code for incomplete write
}
fclose(file);
return 0; // Success
}
Stack traces
In C, handling unrecoverable errors can be achieved using the abort() function, which terminates the program immediately. For recoverable errors, C does not have built-in support for backtraces. However, it is possible to implement a basic form of error handling using return codes or custom error structures to indicate and handle errors in a recoverable manner. To backtrace, developers often resort to manual logging or stack tracing techniques in C.
Nullability and Optionality
In JavaScript, null
is often used to represent a value that is missing, absent or logically uninitialized. For example:
let some = 1;
let none = null;
Rust has no null
and consequently no nullable context to enable. Optional or missing values are instead represented by [Option<T>
][option]. The equivalent of the JavaScript code above in Rust would be:
#include <stddef.h>
//...
int some = 1;
int *none = NULL;
Control flow with optionality
In JavaScript, you may have been using if
/else
statements for controlling the flow when using nullable values.
let max = 10;
if (max !== null && max !== undefined) {
let someMax = max;
console.log(`The maximum is ${someMax}.`); // Output:The maximum is 10.
}
C does not have built-in support for null or undefined values like JavaScript. Instead, it typically uses specific values or flags to indicate absence or special conditions:
#include <stdio.h>
int main() {
int max = 10;
if (max != 0) {
int someMax = max;
printf("The maximum is %d.\n", someMax); // Output: The maximum is 10.
}
return 0;
}
Null-conditional operators
The null-conditional operators (?.
) make dealing with null
in JavaScript more ergonomic.
In C language, there is no direct equivalent to the null-conditional operator ?. found in JavaScript. To handle similar scenarios in C, one can use conditional statements to check for null pointers before accessing members.
let some = "Hello, World!";
let none = null;
console.log(some?.length); // 13
console.log(none?.length); // undefined
#include <stdio.h>
#include <string.h>
int main() {
char* some = "Hello, World!";
char* none = NULL;
printf("%d\n", (some != NULL) ? (int)strlen(some) : -1);
printf("%d\n", (none != NULL) ? (int)strlen(none) : -1);
return 0;
}
Null-coalescing operator
The null-coalescing operator (??
) is typically used to default to another value when a nullable is null
:
let some = 1;
let none = null;
console.log(some ?? 0); // 1
console.log(none ?? 0); // 0
In C, there is no direct equivalent to the null-coalescing operator (??) as in JavaScript. However, it is possible to achieve similar functionality using conditional operators.
#include <stdio.h>
int main() {
int some = 1;
int *none = NULL;
printf("%d\n", some); // 1
printf("%d\n", none != NULL ? *none : 0); // 0
return 0;
}
Null-forgiving operator
In C, there is no direct equivalent to the null-forgiving operator (!) found in languages like C# or Rust. To handle null pointers or avoid null references, C programmers typically use conditional statements or pointer checks to ensure safe memory access. Unlike Rust, C does not have built-in mechanisms for static flow analysis to handle null values implicitly. Therefore, developers need to implement explicit null checks and error handling in C code to manage potential null pointer exceptions effectively.
Discards
Discards express to the compiler and others to ignore the results (or parts) of an expression.
There are multiple contexts where to apply this, for example as a basic example, to ignore the result of an expression. JavaScript doesn't have discards, but you can call a function without assigning a value to any variable to emulate discards. In JavaScript this looks like:
city.getCityInformation(cityName);
In C, discarding the result of a function call is typically achieved by assigning the result to a variable and not using that variable further. In C, casting the result to (void) indicates to the compiler that the return value is intentionally being ignored.
(void)city_get_city_information(city_name);
Discards are also applied for deconstructing "tuples" in JavaScript:
const [_, second] = ["first", "second"];
In C, tuple deconstruction like in JavaScript can be achieved using a similar approach. However, C does not have built-in tuple types like JavaScript. To mimic tuple deconstruction, one can use arrays or structures:
#include <stdio.h>
int main() {
char* values[] = {"first", "second"};
char* second = values[1];
printf("Second value: %s\n", second);
return 0;
}
To achieve struct destructuring similar to Rust in C, one can use a similar approach by defining a struct and then accessing its members selectively.
#include <stdio.h>
struct Point {
int x;
int y;
int z;
};
int main() {
struct Point origin = {0, 0, 0};
switch (origin.x) {
case 0:
printf("x is %d\n", origin.x);
break;
}
return 0;
}
When pattern matching, it is often useful to discard or ignore part of a matching expression. But since there are no discards in JavaScript, and the switch statement of js cannot be used in the same way as rust, you have to emulate this feature in an awkward way:
const _ = ("first", "second");
const result = (_ => {
switch(true) {
case _.includes("first"):
return "first element matched";
default:
return "first element did not match";
}
})();
console.log(result);
and again, in C:
#include <stdio.h>
#include <string.h>
int main() {
const char* first = "first";
const char* second = "second";
const char* result;
if (strcmp(first, "first") == 0) {
result = "first element matched";
} else {
result = "first element did not match";
}
printf("%s\n", result);
return 0;
}
Conversion and Casting
C is statically-typed at compile time. Hence, after a variable is declared, assigning a value of a value of a different type (unless it's implicitly convertible to the target type) to the variable is prohibited. There are several ways to convert types in C.
Implicit conversions
Implicit conversions exist in JavaScript as well as in C (called type coercions). Consider the following example:
let intNumber = 1;
let longNumber = intNumber;
In C:
#include <stdio.h>
int main() {
int int_number = 1;
long long_number = int_number; // No error in C
printf("int_number: %d, long_number: %ld\n", int_number, long_number);
return 0;
}
By assigning the address of s to t, it is possible to achieve a similar implicit conversion in C:
void bar() {
const char *s = "hi";
const char *t = s;
}
See also:
- [Deref coercion]
Explicit conversions
If converting could cause a loss of information, JavaScript requires explicit conversions using a casting expression:
let a = 1.2;
let b = parseInt(a);
C does not handle exceptions during conversions:
#include <stdio.h>
int main() {
int int_number = 1;
long long_number = (long)int_number;
// Additional code for demonstration
printf("Integer: %d\n", int_number);
printf("Long: %ld\n", long_number);
return 0;
}
Custom conversion
In C, type conversion is typically achieved through explicit casting:
#include <stdio.h>
#include <string.h>
typedef struct {
char value[100];
} MyId;
void fromMyIdToString(MyId myId, char* result) {
strcpy(result, myId.value);
}
int main() {
MyId myId;
strcpy(myId.value, "id");
char result[100];
fromMyIdToString(myId, result);
printf("%s\n", result);
return 0;
}
Operator overloading
JavaScript doesn't support operator overloading. Consider the following example in JavaScript:
class Fraction {
constructor(numerator, denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
static add(a, b) {
return new Fraction(a.numerator * b.denominator + b.numerator * a.denominator, a.denominator * b.denominator);
}
toString() {
return `${this.numerator}/${this.denominator}`;
}
}
console.log(Fraction.add(new Fraction(5, 4), new Fraction(1, 2)).toString()); // Output "14/8"
In C, operator overloading is not directly supported. However, it is possible to achieve similar functionality by defining functions that mimic operator behavior.
#include <stdio.h>
typedef struct {
int numerator;
int denominator;
} Fraction;
Fraction addFractions(Fraction f1, Fraction f2) {
Fraction result;
result.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator;
result.denominator = f1.denominator * f2.denominator;
return result;
}
int main() {
Fraction f1 = {5, 4};
Fraction f2 = {1, 2};
Fraction result = addFractions(f1, f2);
printf("%d/%d\n", result.numerator, result.denominator); // Output: 14/8
return 0;
}
Documentation Comments
A third-party tool called JSDoc provides a mechanism to document the API for types using a comment syntax. JSDoc includes a Markdown plugin that automatically converts Markdown-formatted text to HTML. The comment contains structured data representing the comments and the API signatures. Other tools can process that output to provide human-readable documentation in a different form. A simple example in JavaScript:
public class MyClass {}
/**
* This is a document comment for `MyClass`.
* @class
*/
class MyClass {}
C lacks a standardized way to generate documentation from comments like JSDoc, developers can adopt tools like Doxygen to extract structured comments and generate documentation from C code. Doxygen interprets specially formatted comments to produce documentation.Doxygen serves as the tool for generating documentation. In C, Doxygen uses a specific syntax to create documentation comments. For instance, in C using Doxygen:
/*
* This is a comment for the MyStruct struct.
*/
struct MyStruct {
// Members of the struct
};
In JSDoc, the equivalent to Doxygen is jsdoc
.
See also:
- How to write documentation
- [Documentation tests]
Memory Management
In C, memory management is a crucial aspect of programming as it allows developers to allocate and deallocate memory dynamically. Unlike high-level languages with built-in garbage collection mechanisms, C requires manual memory management.
In JavaScript, there is no concept of ownership of memory beyond the GC roots (static fields, local variables on a thread's stack, CPU registers, handles, etc.). It is the GC that walks from the roots during a collection to detemine all memory in use by following references and purging the rest. When designing types and writing code, a JavaScript developer can remain oblivious to ownership, memory management and even how the garbage collector works for the most part, except when performance-sensitive code requires paying attention to the amount and rate at which objects are being allocated on the heap. In contrast, Rust's ownership rules require the developer to explicitly think and express ownership at all times and it impacts everything from the design of functions, types, data structures to how the code is written. On top of that, Rust has strict rules about how data is used such that it can identify at compile-time, data [race conditions] as well as corruption issues (requiring thread-safety) that could potentially occur at run-time. This section will only focus on memory management and ownership.
In C, developers need to manage memory explicitly.
#include <stdio.h>
#include <stdlib.h>
struct Point {
int x;
int y;
};
int main() {
struct Point* a = (struct Point*)malloc(sizeof(struct Point));
a->x = 12;
a->y = 34;
struct Point* b = a; // b now points to the same memory as a
printf("%d, %d\n", a->x, a->y);
free(a); // Freeing the memory explicitly
return 0;
}
In C, there is no explicit concept of ownership. When a is assigned to b, a copy of the Point struct is made, and both a and b can be used independently. Memory management in C is manual, and there is no automatic dropping of resources like in Rust. Therefore, in the C version, the point behind b is not explicitly dropped as in Rust.
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
int main() {
Point a = {12, 34}; // point owned by a
Point b = a; // b owns the point now
printf("%d, %d\n", b.x, b.y); // ok, uses b
return 0;
} // point behind b is not explicitly dropped in C
In C, the equivalent concept to execute code when an instance is dropped is typically achieved using a combination of a struct and functions.
In C, the concept of dropping an object, can be achieved through manual memory management.
In C, a static variable retains its value throughout the program's execution.
In JavaScript, references are shared freely without much thought.
#include <stdio.h>
#include <stdlib.h>
struct Point {
int x;
int y;
};
void point_drop(struct Point* self) {
printf("Point dropped!\n");
free(self);
}
int main() {
struct Point* a = (struct Point*)malloc(sizeof(struct Point));
a->x = 12;
a->y = 34;
struct Point* b = a; // share with b
printf("a = %d, %d\n", a->x, a->y); // okay to use a
printf("b = %d, %d\n", b->x, b->y);
point_drop(a);
return 0;
}
// prints:
// a = 12, 34
// b = 12, 34
// Point dropped!
In C, there are no smart pointers. The equivalent concept in C would involve manual memory management using functions like malloc and free.
struct Point {
int x;
int y;
};
struct Point* stack_point = (struct Point*)malloc(sizeof(struct Point));
stack_point->x = 12;
stack_point->y = 34;
struct Point* heap_point = (struct Point*)malloc(sizeof(struct Point));
heap_point->x = 12;
heap_point->y = 34;
Resource Management
Previous section on memory management explains the differences between JavaScript and C when it comes to GC. It is highly recommended to read it.
This section is limited to providing an example of a fictional database connection involving a SQL connection to be properly closed/disposed/dropped.
class DatabaseConnection {
constructor(connectionString) {
this.connectionString = connectionString;
}
closeConnection() {
// Implementation to close the connection
}
}
// ...closing connection...
DatabaseConnection.prototype.close = function() {
this.closeConnection();
console.log(`Closing connection: ${this.connectionString}`);
};
// Create instances of DatabaseConnection
const db1 = new DatabaseConnection("Server=A;Database=DB1");
const db2 = new DatabaseConnection("Server=A;Database=DB2");
// ...code for making use of the database connection...
// "Dispose" of "db1" and "db2" when their scope ends
#include <stdio.h>
#include <stdlib.h>
// Define a structure to represent DatabaseConnection
typedef struct {
char* connectionString;
} DatabaseConnection;
// Function to create a new DatabaseConnection instance
DatabaseConnection* createDatabaseConnection(const char* connectionString) {
DatabaseConnection* db = (DatabaseConnection*)malloc(sizeof(DatabaseConnection));
db->connectionString = strdup(connectionString);
return db;
}
// Function to close the connection
void closeConnection(DatabaseConnection* db) {
// Implementation to close the connection
printf("Closing connection: %s\n", db->connectionString);
free(db->connectionString);
free(db);
}
int main() {
// Create instances of DatabaseConnection
DatabaseConnection* db1 = createDatabaseConnection("Server=A;Database=DB1");
DatabaseConnection* db2 = createDatabaseConnection("Server=A;Database=DB2");
// ...code for making use of the database connection...
// "Dispose" of "db1" and "db2" when their scope ends
closeConnection(db1);
closeConnection(db2);
return 0;
}
Threading
The C standard library supports threading, synchronisation and concurrency. Also the language itself and the standard library do have basic support for the concepts, a lot of additional functionality is provided by third party libraries and will not be covered in this document.
For threading in C, the pthread library is commonly used:
#include <stdio.h>
#include <pthread.h> // Requires GCC
void *thread_function(void *arg) {
// Thread logic here
return NULL;
}
int main() {
pthread_t thread_id;
pthread_create(&thread_id, NULL, thread_function, NULL);
// Main program logic here
pthread_join(thread_id, NULL);
return 0;
}
For synchronization and concurrency in C, mechanisms like mutexes and semaphores can be utilized. Here is a basic example using a mutex for synchronization:
#include <stdio.h>
#include <pthread.h> // Requires GCC
pthread_mutex_t mutex;
void *thread_function(void *arg) {
pthread_mutex_lock(&mutex);
// Critical section
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread_id;
pthread_mutex_init(&mutex, NULL);
pthread_create(&thread_id, NULL, thread_function, NULL);
// Main program logic here
pthread_join(thread_id, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
To compile the code, use the -pthread flag with gcc:
gcc -pthread main.c -o main
JavaScript is a single-threaded scripting language that does not support multithreading.
Below is a simple JavaScript program that creates a thread (where the thread prints some text to standard output) indirectly by using worker
and then waits for it to end:
// Equivalent JavaScript code using Web Workers
const worker = new Worker(URL.createObjectURL(new Blob([`
self.onmessage = function(event) {
console.log(event.data);
};
`], { type: 'application/javascript' })));
worker.postMessage('Hello from a thread!');
The same code in C would be as follows:
#include <stdio.h>
#include <pthread.h>// Requires GCC
void *thread_function(void *arg) {
printf("Hello from a thread!\n");
return NULL;
}
int main() {
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
pthread_join(thread, NULL); // wait for thread to finish
return 0;
}
In JavaScript, it's possible to send data as an argument to a thread:
const workerCode = `
self.onmessage = function(e) {
let eventData = e.data;
eventData += (" World!");
console.log("Phrase: " + eventData);
};
`;
const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
const data = "Hello";
worker.postMessage(data);
In C, the data is passed to the thread:
#include <stdio.h>
#include <pthread.h>// Requires GCC
#include <string.h>
void* thread_function(void* arg) {
char* data = (char*)arg;
strcat(data, " World!");
return data;
}
int main() {
char data[20] = "Hello";
pthread_t thread;
pthread_create(&thread, NULL, thread_function, (void*)data);
void* result;
pthread_join(thread, &result);
printf("Phrase: %s\n", (char*)result);
return 0;
}
Synchronization
When data is shared between threads, one needs to synchronize read-write access to the data in order to avoid corruption. In JavaScript:
let data = 0;
let workers = [];
let completedWorkers = 0;
for (let i = 0; i < 10; i++) {
let worker = new Worker('data:text/javascript,' + encodeURIComponent(`
let partialData = 0;
for (let j = 0; j < 1000; j++) {
partialData++;
}
self.postMessage(partialData);
`));
worker.onmessage = function(event) {
data += event.data;
completedWorkers++;
if (completedWorkers === 10) {
console.log(data);
workers.forEach(function(worker) {
worker.terminate();
});
}
};
workers.push(worker);
worker.postMessage(null);
}
Using threading and synchronization mechanisms available in C:
#include <stdio.h>
#include <pthread.h> // Requires GCC
#define NUM_THREADS 10
#define NUM_INCREMENTS 1000
int data = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_function(void* arg) {
for (int i = 0; i < NUM_INCREMENTS; i++) {
pthread_mutex_lock(&mutex);
data++;
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main() {
pthread_t threads[NUM_THREADS];
for (int i = 0; i < NUM_THREADS; i++) {
pthread_create(&threads[i], NULL, thread_function, NULL);
}
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("%d\n", data);
return 0;
}
Producer-Consumer
The producer-consumer pattern is very common to distribute work between threads where data is passed from producing threads to consuming threads without the need for sharing or locking.
const workerCode = `
self.onmessage = function() {
const messages = [];
for (let n = 1; n < 10; n++) {
messages.push("Message #" + n);
}
self.postMessage(messages);
};
`;
const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
// The main thread acts as a consumer here
worker.onmessage = function(event) {
const messages = event.data;
messages.forEach(message => console.log(message));
};
// Start the worker
worker.postMessage(null);
A rough translation of the above JavaScript example in C would look as follows:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h> // Requires GCC
#define BUFFER_SIZE 10
void* producer(void* arg) {
for (int n = 1; n < 10; n++) {
printf("Message #%d\n", n);
}
pthread_exit(NULL);
}
int main() {
pthread_t tid;
pthread_create(&tid, NULL, producer, NULL);
// main thread acts as the consumer
pthread_join(tid, NULL);
return 0;
}
Testing
Test organization
JavaScript solutions use separate projects to host test code, irrespective of the test framework being used (Jest, Mocha, etc.) and the type of tests (unit or integration) being written. The test code therefore lives in a separate assembly than the application or library code being tested. In C, developers use a third party tool called CUnit for testing.
See also:
Running tests
CUnit supports running all tests in all registered suites, but individual tests or suites can also be run. During each run, the framework keeps track of the number of suites, tests, and assertions run, passed, and failed. Note that the results are cleared each time a test run is initiated (even if it fails).
For more information, see "Running Tests in CUnit".
Output in Tests
For very complex integration or end-to-end test, developers sometimes log what's happening during a test. The actual way they do this varies with each test framework in C.
Assertions
JavaScript users have multiple ways to assert, depending on the framework being used. For example, an assertion in Jest might look like:
test('something has the right length', () => {
let value = "something";
expect(value.length).toBe(9);
});
An example that only uses vanilla JavaScript:
function somethingIsTheRightLength() {
let value = "something";
console.assert(value.length === 9);
}
somethingIsTheRightLength();
C does not require a separate framework like CUnit for asserting.
Below is an example:
#include <assert.h>
#include <string.h>
int main() {
void test_something_is_the_right_length() {
const char *value = "something";
assert(strlen(value) == 9);
}
test_something_is_the_right_length(); // Call the test function
return 0;
}
The standard library does not offer anything in the direction of data-driven tests.
Mocking
There are libraries for C too, like umock_c
, that can help with mocking. However, it is also possible to use conditional compilation.
The example below shows mocking of a stand-alone function var_os
and returns the value of an environment variable. It conditionally imports a mocked version of the var_os
function:
#include <stdio.h>
#ifdef TEST
#define var_os var_os_mock
char* var_os_mock() {
return "Mocked Value";
}
#else
char* var_os() {
return "Actual Value";
}
#endif
void get_env() {
char* value = var_os();
printf("Environment Variable Value: %s\n", value);
}
int main() {
#ifndef TEST
get_env();
#endif
return 0;
}
Code coverage
There is sophisticated tooling for JavaScript when it comes to analyzing test code coverage, such as Jest.
In C, one can implement code coverage functionalities by utilizing tools like gcov or LLVM's coverage tools. These tools can help collect test code coverage data in C programs. By incorporating these tools into the C codebase, developers can achieve code coverage analysis.
Benchmarking
Running benchmarks in C is done via third party tools.
JavaScript users can make use of Benchmark.js
library to benchmark methods and track their performance. The equivalent of Benchmark.js
in C is a tool named ansibench
.
As per its documentation, ansibench
collects and stores statistical information from run to run and can automatically detect performance regressions as well as measuring optimizations.
Logging and Tracing
For most cases, console.log()
is a good default choice for JavaScript, since it works with a variety of built-in and third-party logging providers. In JavaScript, a minimal example for structured logging could look like:
let day = "Thursday";
console.log("Hello ", day); // Hello Thursday.
In C, there are no built-in loggers. Developers use printf
based methods to log.
Conditional Compilation
Both JavaScript and C are providing the possibility for compiling specific code based on external conditions.
JavaScript doesn't support conditional compilation natively. However, it is possible to use some third-party tool like babel-plugin-preprocessor
in order to control conditional compilation.
//#if DEBUG
console.log("Debug");
//#else
console.log("Not debug");
//#endif
An example that uses vanilla JavaScript:
let isDebug = true;
if(isDebug)
{
window.eval(`
console.log("Debug");
`);
} else {
window.eval(`
console.log("Not debug");
`);
}
Conditional compilation in C allows developers to include or exclude sections of code during the compilation process based on certain conditions. This feature is particularly useful when different versions of a program need to be generated for various platforms or configurations without modifying the source code.
In C, conditional compilation is achieved using preprocessor directives, which are processed before the actual compilation of the code. The #ifdef, #ifndef, #else, #elif, and #endif directives are commonly used for conditional compilation.
Here is a simple example to illustrate conditional compilation in C:
#include <stdio.h>
#define DEBUG 1
int main() {
#ifdef DEBUG
printf("Debug mode is enabled.\n");
#else
printf("Debug mode is disabled.\n");
#endif
return 0;
}
Environment and Configuration
Accessing environment variables
JavaScript doesn't provide access to environment variables natively. However, some non-browser JavaScript runtimes, such as Node.js and Node provides access to environment variables.
In Node.js:
const name = "EXAMPLE_VARIABLE";
let value = process.env[name];
if (!value) {
console.log(`Variable '${name}' not set.`);
} else {
console.log(`Variable '${name}' set to '${value}'.`);
}
In Deno:
const name = "EXAMPLE_VARIABLE";
let value = Deno.env.get(name);
if (!value) {
console.log(`Variable '${name}' not set.`);
} else {
console.log(`Variable '${name}' set to '${value}'.`);
}
In C programming, environmental variables can be accessed using the getenv() function provided by the standard library <stdlib.h>. This function allows a program to retrieve the value of a specific environmental variable by providing its name as an argument.
Here is a simple example demonstrating how to retrieve the value of an environmental variable named PATH:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *path_value = getenv("PATH");
if (path_value != NULL) {
printf("The value of PATH is: %s\n", path_value);
} else {
printf("PATH is not set.\n");
}
return 0;
}
In C, accessing environment variables at compile time involves utilizing preprocessor directives and macros to incorporate the values of environment variables during the compilation phase. This process allows for the configuration of the program based on the environment where it will run without the need for runtime modifications.
One common approach to achieve this functionality is by using the -D flag in the compiler command to define a macro with the value of the desired environment variable. For instance, consider an environment variable MY_ENV_VAR that you want to access at compile time. You can pass this variable's value to the compiler using the -D flag as follows:
#include <stdio.h>
#ifndef MY_ENV_VAR
#define MY_ENV_VAR "default_value"
#endif
int main() {
printf("Value of MY_ENV_VAR: %s\n", MY_ENV_VAR);
return 0;
}
When compiling the program, it is possible to set the value of MY_ENV_VAR by defining it during compilation:
gcc -o myprogram myprogram.c -DMY_ENV_VAR='"custom_value"'
Configuration
JavaScript doesn't support configurations, neither nor C.
LINQ
This section discusses LINQ within the context and for the purpose of querying or transforming sequences and typically collections like lists, sets and dictionaries.
Enumerable items
The equivalent of enumerable items in C is enum
. In JavaScript, there is forEach
:
let values = [1, 2, 3, 4, 5];
let output = '';
values.forEach((value, index) => {
if (index > 0)
output += ', ';
output += value;
});
console.log(output); // Outputs: 1, 2, 3, 4, 5
In C, the equivalent is simply for
:
u#include <stdio.h>
#include <string.h> // Include the string.h header file
int main() {
int values[] = {1, 2, 3, 4, 5};
char output[50] = "";
for (int i = 0; i < 5; i++) {
if (i > 0)
sprintf(output + strlen(output), ", ");
sprintf(output + strlen(output), "%d", values[i]);
}
printf("%s\n", output); // Outputs: 1, 2, 3, 4, 5
return 0;
}
The for
loop over an iterable essentially gets desuraged to the following:
#include <stdio.h>
#include <string.h>
int main() {
int values[] = {1, 2, 3, 4, 5};
char output[50] = "";
int i;
for (i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
if (i > 0) {
sprintf(output + strlen(output), ", ");
}
sprintf(output + strlen(output), "%d", values[i]);
}
printf("%s\n", output);
return 0;
}
Operators
JavaScript and C don't natively support LINQ, but there is a project called LINQ.js that implements LINQ in C# for JavaScript, and a project called linq4c that implements LINQ in C# for C.
Operators in LINQ are implemented in the form of LINQ.js extension methods that can be chained together to form a set of operations, with the most common forming a query over some sort of data source. LINQ.js also offers a SQL-inspired query syntax with clauses like from
, where
, select
, join
and others that can serve as an alternative or a companion to method chaining. Many imperative loops can be re-written as much more expressive and composable queries in LINQ.
Deferred execution (laziness)
Many operators in LINQ are designed to be lazy such that they only do work when absolutely required. This enables composition or chaining of several operations/methods without causing any side-effects.
In both cases, this allows infinite sequences to be represented, where the underlying sequence is infinite, but the developer decides how the sequence should be terminated. The following example shows this in JavaScript:
function* infiniteRange() {
for (let i = 0; ; ++i) {
yield i;
}
}
for (let x of infiniteRange()) {
if (x < 5) {
console.log(x); // Prints "0 1 2 3 4"
} else {
break;
}
}
C supports the same concept through for
:
#include <stdio.h>
int main() {
int value;
for (value = 0; value < 5; value++) {
printf("%d ", value); // Prints "0 1 2 3 4"
}
return 0;
}
Iterator Methods (yield
)
JavaScript has the yield
keword that enables the developer to quickly write an iterator method. C doesn't support coroutines natively, but there is a tutorial of implementing coroutines in C.
Meta Programming
Metaprogramming can be seen as a way of writing code that writes/generates other code.
JavaScript has the concept of metaprogramming, but it refers to intercepting and defining basic language operations, which is different from metaprogramming in C, C# or Rust. There is a JavaScript source generator called hygen, but it does not call itself a "metaprogramming tool".
C does not support metaprogramming natively, however, third party tools, like metalang99, exists.
C does not support reflection.
Function-like macros
Function-like macros in C are in the following form: #define
The following code snippet defines a function-like macro named print_something
, which is generating a print_it
method for printing the "Something" string.
#include <stdio.h>
#define print_something() printf("Something\n")
int main() {
print_something();
return 0;
}
Derive macros
Derive macros are not supported in C and JavaScript.
Attribute macros
Attribute macros are not supported in C and JavaScript.
Asynchronous Programming
Both JavaScript and C support asynchronous programming models, which look similar to each other with respect to their usage. The following example shows, on a very high level, how async code looks like in JavaScript:
async function printDelayed(message, cancellationToken) {
await new Promise(resolve => setTimeout(resolve, 1000));
return `Message: ${message}`;
}
C code is structured differently:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h> // Requires GCC
#include <unistd.h>
void* printDelayed(void* args) {
char* message = (char*)args;
sleep(1); // Simulating a delay of 1 second
char* result = malloc(strlen(message) + 10); // Allocating memory for the result
sprintf(result, "Message: %s", message);
return result;
}
int main() {
char* message = "Hello, World!";
pthread_t tid;
void* result;
pthread_create(&tid, NULL, printDelayed, (void*)message);
pthread_join(tid, &result);
printf("%s\n", (char*)result);
free(result); // Freeing allocated memory
return 0;
}
See also:
Executing tasks
From the following example the PrintDelayed
method executes, even though it is not awaited:
let cancellationToken = undefined;
printDelayed("message", cancellationToken); // Prints "message" after a second.
await new Promise(resolve => setTimeout(resolve, 2000));
async function printDelayed(message, cancellationToken) {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(message);
}
In C:
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#define sleep(x) Sleep(x * 1000)
#else
#include <unistd.h>
#endif
void printDelayed(char* message) {
sleep(1); // Delay for 1 second
printf("%s\n", message);
}
int main() {
char* message = "message";
printDelayed(message); // Prints "message" after a second.
sleep(2); // Delay for 2 seconds
return 0;
}
Task cancellation
The previous JavaScript examples included passing a CancellationToken
to asynchronous methods, as is considered best practice in JavaScript. CancellationToken
s can be used to abort an asynchronous operation.
In C language, the concept ofcancellation can be implemented using custom structures and functions.
Executing multiple Tasks
In JavaScript, Promise.race
is frequently used to handle the execution of multiple tasks.
Promise.race
completes as soon as any task completes.
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const delayMessage = async (delayTime) => {
await delay(delayTime);
return `Waited ${delayTime / 1000} second(s).`;
};
const delay1 = delayMessage(1000);
const delay2 = delayMessage(2000);
Promise.race([delay1, delay2]).then(result => {
console.log(result); // Output: Waited 1 second(s).
});
In C, to achieve similar functionality to Promise.race in JavaScript, one can use a combination of threads and synchronization mechanisms like mutexes or semaphores. By creating multiple threads that perform tasks concurrently and using synchronization to determine which thread finishes first, a similar behavior to Promise.race can be achieved in C. Below is a simplified example in C:
#include <stdio.h>
#include <pthread.h> // Requires GCC
#include <unistd.h>
void* delayMessage(void* delayTime) {
int ms = *((int*)delayTime);
usleep(ms * 1000); // Convert milliseconds to microseconds
char* message = malloc(100 * sizeof(char));
sprintf(message, "Waited %d second(s).", ms / 1000);
return message;
}
int main() {
pthread_t thread1, thread2;
int delay1 = 1000;
int delay2 = 2000;
char* result1;
char* result2;
pthread_create(&thread1, NULL, delayMessage, (void*)&delay1);
pthread_create(&thread2, NULL, delayMessage, (void*)&delay2);
pthread_join(thread1, (void**)&result1);
pthread_join(thread2, (void**)&result2);
printf("%s\n", result1); // Output: Waited 1 second(s).
free(result1);
free(result2);
return 0;
}
Multiple consumers
In JavaScript a Promise
can be used across multiple consumers. All of them can await the task and get notified when the task is completed or failed.
In C:
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h> // For Sleep(), DWORD
#else
#include <unistd.h> // For sleep()
#include <sys/types.h> // For pid_t
#include <sys/wait.h> // For wait()
#endif
void background_operation() {
#ifdef _WIN32
Sleep(2000);
#else
sleep(2);
#endif
printf("Background operation completed.\n");
}
#ifdef _WIN32
BOOL WINAPI signal_handler(DWORD dwCtrlType) {
if (dwCtrlType == CTRL_C_EVENT) {
printf("Signal received. Cancelling background operation.\n");
exit(0);
}
return TRUE;
}
#else
void signal_handler(int signal) {
if (signal == SIGINT) {
printf("Signal received. Cancelling background operation.\n");
exit(0);
}
}
#endif
int main() {
#ifdef _WIN32
SetConsoleCtrlHandler(signal_handler, TRUE);
#else
signal(SIGINT, signal_handler);
#endif
DWORD pid = GetCurrentProcessId(); // Get current process ID
printf("Process ID: %lu\n", pid);
// Fork and perform background operation
// ...
wait(NULL);
printf("Parent process waiting for child process to finish.\n");
return 0;
}
Asynchronous iteration
C does not yet have an API for asynchronous iteration in the standard library.
In JavaScript, writing async iterators has comparable syntax to when writing synchronous iterators:
async function* RangeAsync(start, count) {
for (let i = 0; i < count; i++) {
await new Promise(resolve => setTimeout(resolve, i * 1000));
yield start + i;
}
}
(async () => {
for await (const item of RangeAsync(10, 3)) {
console.log(item + " "); // Prints "10 11 12".
}
})();
In C:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct {
int start;
int count;
} Range;
void RangeAsync(Range range) {
for (int i = 0; i < range.count; i++) {
usleep(i * 1000000); // Simulating async delay in microseconds
printf("%d ", range.start + i);
}
}
int main() {
Range range = {10, 3};
RangeAsync(range);
return 0;
}
Project Structure
The JavaScript standard does not specify a specification for the structure of the project. Generally speaking, all the files in a JavaScript library are usually placed in a folder named after the library. The following is a common JavaScript project specification:
.
+-- src/
|Â Â +-- project1.js
+-- styles/
|Â Â +-- style1.js
+-- examples/
|Â Â +-- some-example.js
+-- tests/
+-- some-integration-test.js
The C standard does not specify a specification for the structure of the project.
Managing large projects
For very large projects in C, different IDEs and different toolchains have different ways to manage large projects.
Managing dependency versions
There is no concept of dependency in the C standard, different IDEs and different toolchains have different ways to manage dependency versions.
Compilation and Building
JavaScript CLI
There is no concept of CLI in the JavaScript standard. People often use non-browser runtimes such as Node.js and Deno to act as CLIs.
There is no concept of CLI in the C standard, different IDEs and different toolchains have different CLIs.
Building
When building JavaScript, the scripts coming from dependent packages are generally co-located with the project's output assembly. C compilers compiles the project sources, except the C compiler statically links all code into a single, platform-dependent, binary.
Developers use different ways to prepare a JavaScript executable for distribution, either as a framework-dependent deployment (FDD) or self-contained deployment (SCD). In Rust, there is no way to let the build output already contains a single, platform-dependent binary for each target.
In C, the build output is, again, a platform-dependent, compiled library for each library target.
Dependencies
There is no concept of dependency in the JavaScript standard. However, some JavaScript runtimes, such as Node.js and Deno, have the concept of dependencies. In Node.js and Deno, the contents of a project file (package.json
) define the build options and dependencies. A typical project file will look like:
{
"name": "your-project-name",
"version": "1.0.0",
"description": "Your project description",
"dependencies": {
"linq": "4.0.3"
}
}
There is no concept of dependency in the C standard, different IDEs and different toolchains have different ways to manage dependencies.
Packages
There is no concept of packages in the JavaScript standard. However, some JavaScript runtimes, such as Node.js, have the concept of packages. NPM is most commonly used to install packages for Node.js, and various tools supported it. For example, adding a Node.js package reference with the Node,js CLI will add the dependency to the project file:
npm install linq
The most common package registry for Node.js is npmjs.com .
There is no concept of packages in the C standard, different IDEs and different toolchains have different ways to manage packages.
Static code analysis
ESLint is an analyzer that provide code quality as well as code-style analysis. The equivalent linting tool in C is Clang Static Analyzer or other tools.
File Operations
This chapter introduce file operations in C and JavaScript.
JavaScript has no built-in file operation APIs, so third party JavaScript runtimes are required.
Text File Operations
Write and read a text file in C:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file;
char text[] = "sample text";
// Write to file
file = fopen("test.txt", "w");
if (file == NULL) {
printf("Error opening file!\n");
exit(1);
}
fprintf(file, "%s", text);
fclose(file);
// Read from file
char buffer[255];
file = fopen("test.txt", "r");
if (file == NULL) {
printf("Error opening file!\n");
exit(1);
}
fscanf(file, "%s", buffer);
printf("Content of test.txt: %s\n", buffer);
fclose(file);
return 0;
}
In Node.js:
const fs = require('fs');
// Write to file
fs.writeFileSync('test.txt', 'sample text');
// Read from file
const data = fs.readFileSync('test.txt', 'utf8');
console.log(data);
In Deno:
// to run the program, execute `deno run --allow-all text-operations.js`
const text = "sample text";
const encoder = new TextEncoder();
const data = encoder.encode(text);
await Deno.writeFile("test.txt", data);
const file = await Deno.open("test.txt");
const fileContent = new Uint8Array(100);
await Deno.read(file.rid, fileContent);
const decoder = new TextDecoder();
const decodedText = decoder.decode(fileContent);
console.log(decodedText);
Deno.close(file.rid);
Binary File Operations
In the following example, the binary data of the ASCII character a
will be written to test.bin
, and the content of test.bin
will be read and displayed on the console. The size of the binary file is determined at runtime.
In C:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *file;
char data = 'a';
// Writing binary data to test.bin
file = fopen("test.bin", "wb");
fwrite(&data, sizeof(char), 1, file);
fclose(file);
// Reading and displaying binary data from test.bin
file = fopen("test.bin", "rb");
fseek(file, 0, SEEK_END);
long fileSize = ftell(file);
rewind(file);
char *buffer = (char *)malloc(fileSize);
fread(buffer, sizeof(char), fileSize, file);
fclose(file);
for (int i = 0; i < fileSize; i++) {
printf("%c", buffer[i]);
}
free(buffer);
return 0;
}
In Node.js:
const fs = require('fs');
const data = Buffer.from('a', 'ascii');
fs.writeFile('test.bin', data, (err) => {
if (err) throw err;
fs.readFile('test.bin', (err, data) => {
if (err) throw err;
console.log(data.toString('ascii'));
});
});
In Deno:
// to run the program, execute `deno run --allow-all binary-operations.js`
const encoder = new TextEncoder();
const data = encoder.encode('a');
await Deno.writeFile("test.bin", data);
const file = await Deno.open("test.bin");
const content = new Uint8Array(await Deno.readAll(file));
Deno.close(file.rid); // Close the file using the file descriptor
const decoder = new TextDecoder();
console.log(decoder.decode(content));
Copying File Operations
The following code will copy test.txt
to test2.txt
.
In C:
#include <stdio.h>
int main() {
FILE *source, *target;
char ch;
source = fopen("test.txt", "r");
if (source == NULL) {
printf("Error opening source file.\n");
return 1;
}
target = fopen("test2.txt", "w");
if (target == NULL) {
fclose(source);
printf("Error opening target file.\n");
return 1;
}
while ((ch = fgetc(source)) != EOF) {
fputc(ch, target);
}
printf("File copied successfully.\n");
fclose(source);
fclose(target);
return 0;
}
In Node.js:
const fs = require('fs');
fs.copyFile('test.txt', 'test2.txt', (err) => {
if (err) throw err;
console.log('test.txt was copied to test2.txt');
});
In Deno:
// to run the program, execute `deno run --allow-all copying-operations.js`
const sourceFile = await Deno.open('test.txt');
const destinationFile = await Deno.create('test2.txt');
await Deno.copy(sourceFile, destinationFile);
sourceFile.close();
destinationFile.close();
console.log('File copied successfully!');
Renaming File Operations
The following code will rename old_name.txt
to new_name.txt
.
In C:
#include <stdio.h>
int main() {
const char *old_name = "old_name.txt";
const char *new_name = "new_name.txt";
if (rename(old_name, new_name) == 0) {
printf("File renamed successfully.\n");
} else {
perror("Error renaming file");
}
return 0;
}
In Node.js:
const fs = require('fs');
const oldPath = 'old_name.txt';
const newPath = 'new_name.txt';
fs.rename(oldPath, newPath, (err) => {
if (err) {
console.error(err);
return;
}
console.log('File renamed successfully!');
});
In Deno:
// to run the program, execute `deno run --allow-all renaming-operations.js`
// Define the old and new file names
const oldName = "old_name.txt";
const newName = "new_name.txt";
// Rename the file synchronously
Deno.renameSync(oldName, newName);
console.log(`File ${oldName} has been renamed to ${newName} successfully.`);
Moving File Operations
The following code will move test.txt
to ./test_folder/test.txt
.
In C:
#include <stdio.h>
int main() {
if(rename("test.txt", "./test_folder/test.txt") == 0) {
printf("File moved successfully.\n");
} else {
perror("Error moving file");
}
return 0;
}
In Node.js:
const fs = require('fs');
const sourcePath = 'test.txt';
const destinationPath = './test_folder/test.txt';
fs.rename(sourcePath, destinationPath, (err) => {
if (err) {
console.error(`Error moving the file: ${err}`);
} else {
console.log('File moved successfully!');
}
});
In Deno:
// to run the program, execute `deno run --allow-all moving-operations.js`
const oldFilePath = 'test.txt';
const newFilePath = './test_folder/test.txt';
await Deno.rename(oldFilePath, newFilePath);
console.log(`File moved successfully from ${oldFilePath} to ${newFilePath}`);
Deleting File Operations
The following code will delete test.txt
.
In C:
#include <stdio.h>
int main() {
if (remove("test.txt") == 0) {
printf("File deleted successfully.\n");
} else {
printf("Error deleting the file.\n");
}
return 0;
}
In Node.js:
const fs = require('fs');
const filePath = 'test.txt';
fs.unlink(filePath, (err) => {
if (err) {
console.error(`Error deleting the file: ${err}`);
return;
}
console.log('File deleted successfully');
});
In Deno:
// to run the program, execute `deno run --allow-all deleting-operations.js`
try {
Deno.removeSync("test.txt");
console.log("File 'test.txt' has been deleted successfully.");
} catch (error) {
console.error("An error occurred:", error);
}
Folder Operations
The following code will create a folder called test_folder
.
In C:
#include <stdio.h>
#ifdef _WIN32
#include <direct.h>
#define CREATE_DIR(path) _mkdir(path)
#else
#include <sys/stat.h>
#define CREATE_DIR(path) mkdir(path, 0777)
#endif
int main() {
char* folderName = "test_folder";
if (CREATE_DIR(folderName) == 0) {
printf("Folder created successfully.\n");
} else {
printf("Failed to create folder.\n");
}
return 0;
}
In Node.js:
const fs = require('fs');
const folderName = 'test_folder';
fs.mkdir(folderName, (err) => {
if (err) {
console.error(err);
return;
}
console.log(`Folder '${folderName}' created successfully.`);
});
In Deno:
// to run the program, execute `deno run --allow-all folder-operations.js`
const { mkdir } = Deno;
const folderName = "test_folder";
try {
await mkdir(folderName);
console.log(`Folder '${folderName}' created successfully.`);
} catch (error) {
console.error(`Error creating folder: ${error.message}`);
}